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

nasa / trick / 20284747174

16 Dec 2025 10:27PM UTC coverage: 55.585% (-0.001%) from 55.586%
20284747174

Pull #2016

github

web-flow
Merge d52b33b7b into 56218edde
Pull Request #2016: better support set_cycle in drgroup

5 of 7 new or added lines in 1 file covered. (71.43%)

2 existing lines in 1 file now uncovered.

12511 of 22508 relevant lines covered (55.58%)

306684.89 hits per line

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

82.97
/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
    if(inited) {
41✔
NEW
181
        reset_cycle_data_from_curr_tic();
×
182
    }
183
    return(0) ;
41✔
184
}
185

186
int Trick::DataRecordGroup::add_cycle(double in_cycle)
5✔
187
{
188
    logging_rates.emplace_back(in_cycle);
5✔
189
    if(inited) {
5✔
NEW
190
        reset_cycle_data_from_curr_tic();
×
191
    }
192
    return(0);
5✔
193
}
194

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

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

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

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

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

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

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

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

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

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

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

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

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

262
    return 0 ;
645✔
263
}
264

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

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

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

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

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

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

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

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

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

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

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

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

335
    return(0) ;
4,647✔
336

337
}
338

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

341
    REF2 * ref2 ;
342

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

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

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

358
    return(0) ;
6✔
359

360
}
361

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

374
    return true;
538✔
375
}
376

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

388
    unsigned int jj ;
389
    int ret ;
390

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

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

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

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

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

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

437
    write_header() ;
55✔
438

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

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

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

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

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

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

475
    write_job->next_tics = curr_tics;
55✔
476

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

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

486
    return(0) ;
55✔
487

488
}
489

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

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

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

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

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

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

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

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

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

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

550
    return 0 ;
12✔
551
}
552

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

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

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

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

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

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

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

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

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

617
    clear_checkpoint_vars() ;
8✔
618

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

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

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

628
    // Account for the fact that the current time tics is actually already passed for a checkpoint.
629
    reset_cycle_data_from_curr_tic();
8✔
630

631
    return 0 ;
8✔
632
}
633

634
void Trick::DataRecordGroup::reset_cycle_data_from_curr_tic()
8✔
635
{
636
    long long curr_tics = exec_get_time_tics();
8✔
637
    advance_log_tics_given_curr_tic(curr_tics);
8✔
638

639
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
8✔
640
    write_job->cycle_tics = write_job->next_tics - curr_tics;
8✔
641
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
8✔
642
}
8✔
643

644
int Trick::DataRecordGroup::write_header() {
55✔
645

646
    unsigned int jj ;
647
    std::string header_name ;
110✔
648
    std::fstream out_stream ;
110✔
649

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

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

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

661
    format_specific_header(out_stream) ;
55✔
662

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

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

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

687
    return(0) ;
55✔
688

689
}
690

691
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,222✔
692

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

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

713
        }
714

715
        if ( freq == DR_Always || change_detected == true ) {
4,754,222✔
716

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

725
            curr_time = in_time ;
4,753,428✔
726

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

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

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

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

813
    return(0) ;
4,754,222✔
814
}
815

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

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

831
        if(logNextTic == min_tic)
22,034,453✔
832
        {
833
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
61✔
834
        }
835

836
        if(logNextTic < ticOfCycleToProcess)
22,034,453✔
837
        {
838
            ticOfCycleToProcess = logNextTic;
15,511,119✔
839
        }
840
    }
841

842
    return ticOfCycleToProcess;
4,754,285✔
843
}
844

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

861
int Trick::DataRecordGroup::write_data(bool must_write) {
408,911✔
862

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

867
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
408,911✔
868

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

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

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

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

889
        }
890

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

898
        pthread_mutex_unlock(&buffer_mutex) ;
44,476✔
899

900
    }
901

902
    return 0 ;
408,861✔
903
}
904

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

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

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

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

922
    remove_all_variables();
52✔
923

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

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

935
    return 0 ;
52✔
936
}
937

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

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