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

nasa / trick / 21770317411

07 Feb 2026 12:07AM UTC coverage: 55.718% (+0.09%) from 55.624%
21770317411

Pull #2016

github

web-flow
Merge 54a41bd5a into d49130d35
Pull Request #2016: better support set_cycle in drgroup

65 of 78 new or added lines in 1 file covered. (83.33%)

13 existing lines in 1 file now uncovered.

12570 of 22560 relevant lines covered (55.72%)

313303.46 hits per line

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

83.06
/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,692✔
41
    buffer = last_value = NULL ;
5,692✔
42
    ref = NULL ;
5,692✔
43
    ref_searched = false ;
5,692✔
44
}
5,692✔
45

46
Trick::DataRecordBuffer::~DataRecordBuffer() {
636✔
47
    if ( buffer ) {
636✔
48
        free(buffer) ;
621✔
49
    }
50
    if ( last_value ) {
636✔
51
        free(last_value) ;
624✔
52
    }
53

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

58

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

64
void Trick::LoggingCycle::set_rate(double rate_in)
681✔
65
{
66
    rate_in_seconds = rate_in;
681✔
67
    rate_in_tics = (long long)round(rate_in * Trick::JobData::time_tic_value);
681✔
68
}
681✔
69

70
long long Trick::LoggingCycle::calc_next_tics_on_or_after_input_tic(long long input_tic, long long cycle_tic)
22,035,578✔
71
{
72
    long long next_tic;
73
    if((input_tic % cycle_tic) != 0)
22,035,578✔
74
    {
75
        next_tic = (input_tic/cycle_tic) * cycle_tic + cycle_tic;
15,115,302✔
76
    } else 
77
    {
78
        next_tic = input_tic;
6,920,276✔
79
    }
80
    return next_tic;
22,035,578✔
81
}
82

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

111
    union {
112
        long l;
113
        char c[sizeof(long)];
114
    } byte_order_union;
115

116
    byte_order_union.l = 1;
616✔
117
    if (byte_order_union.c[sizeof(long) - 1] != 1) {
616✔
118
        byte_order = "little_endian" ;
616✔
119
    } else {
120
        byte_order = "big_endian" ;
×
121
    }
122

123
    // sim object name
124
    name = std::string("trick_data_record_group_") + in_name ;
616✔
125

126
    configure_jobs(dr_type) ;
616✔
127

128
    add_time_variable() ;
616✔
129

130
    logging_rates.emplace_back(cycle);
616✔
131
}
616✔
132

133
Trick::DataRecordGroup::~DataRecordGroup() {
44✔
134
    free((void *)time_value_attr.units) ;
44✔
135
}
44✔
136

137
int Trick::DataRecordGroup::call_function( Trick::JobData * curr_job ) {
5,192,013✔
138

139
    int ret = 0 ;
5,192,013✔
140

141
    switch (curr_job->id) {
5,192,013✔
142
        case 1:
46✔
143
            ret = init() ;
46✔
144
            break ;
46✔
145
        case 2:
437,050✔
146
            ret = write_data(false) ;
437,050✔
147
            break ;
437,050✔
148
        case 3:
13✔
149
            ret = checkpoint() ;
13✔
150
            break ;
13✔
151
        case 4:
13✔
152
            clear_checkpoint_vars() ;
13✔
153
            break ;
13✔
154
        case 5:
9✔
155
            ret = restart() ;
9✔
156
            break ;
9✔
157
        case 6:
46✔
158
            ret = shutdown() ;
46✔
159
            break ;
46✔
160
        default:
4,754,836✔
161
            ret = data_record(exec_get_sim_time()) ;
4,754,836✔
162
            break ;
4,754,836✔
163
    }
164

165
    return(ret) ;
5,192,013✔
166

167
}
168

169
double Trick::DataRecordGroup::call_function_double( Trick::JobData * curr_job ) {
×
170
    (void) curr_job ;
171
    return(0.0) ;
×
172
}
173

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

185
const std::string & Trick::DataRecordGroup::get_group_name() {
44✔
186
    return group_name ;
44✔
187
}
188

NEW
189
size_t Trick::DataRecordGroup::get_num_rates(){
×
NEW
190
    return logging_rates.size();
×
191
}
192

NEW
193
double Trick::DataRecordGroup::get_rate(const size_t rateIdx)
×
194
{
NEW
195
    if(rateIdx < logging_rates.size())
×
196
    {
NEW
197
        return logging_rates[rateIdx].rate_in_seconds;
×
198
    }
NEW
199
    return -1.0;
×
200
}
201

202
int Trick::DataRecordGroup::set_cycle( double in_cycle ) {
52✔
203
    return set_rate(0, in_cycle);    
52✔
204
}
205

206
int Trick::DataRecordGroup::add_cycle(double in_cycle)
6✔
207
{
208
    logging_rates.emplace_back(in_cycle);
6✔
209
    set_rate(logging_rates.size()-1, in_cycle);
6✔
210
    return (int)logging_rates.size()-1;
6✔
211
}
212

213
int Trick::DataRecordGroup::set_rate(const size_t rate_idx, const double rate_in)
62✔
214
{
215
    if(rate_idx >= logging_rates.size())
62✔
216
    {
NEW
217
        message_publish(MSG_ERROR, "DataRecordGroup ERROR: set_rate: invalid rate idx %lu\n", rate_idx);
×
NEW
218
        return 1;
×
219
    }
220
    if(inited) {
62✔
221
        int ret = check_if_rate_is_valid(rate_in);
12✔
222
        if(ret)
12✔
223
        {
224
            emit_rate_error(ret, rate_idx, rate_in);
3✔
225
            message_publish(MSG_ERROR, "DataRecordGroup ERROR: Rejecting runtime set_rate(%lu, %.16g)\n", rate_idx, rate_in);
3✔
226
            return 1;
3✔
227
        }
228
        long long prev_log_tics = (long long)round(curr_time_dr_job * Trick::JobData::time_tic_value);
9✔
229
        long long curr_time_tic = exec_get_time_tics();
9✔
230
        LoggingCycle & curLog = logging_rates[rate_idx];
9✔
231
        curLog.set_rate(rate_in);
9✔
232
        curLog.next_cycle_in_tics = LoggingCycle::calc_next_tics_on_or_after_input_tic(curr_time_tic, curLog.rate_in_tics);
9✔
233
        if(curLog.next_cycle_in_tics == curr_time_tic && curLog.next_cycle_in_tics == prev_log_tics)
9✔
234
        {
NEW
235
            curLog.next_cycle_in_tics += curLog.rate_in_tics;
×
236
        }
237
        for(auto & logCycle : logging_rates)
27✔
238
        {
239
            logCycle.next_cycle_in_tics = 0;
18✔
240
        }
241
        advance_log_tics_given_curr_tic(curr_time_tic-1);
9✔
242
        write_job->next_tics = calculate_next_logging_tic(curr_time_tic-1);
9✔
243

244
        long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
9✔
245
        write_job->cycle_tics = next_next_tics - write_job->next_tics;
9✔
246
        write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
9✔
247
    } else {
248
        logging_rates[rate_idx].set_rate(rate_in);
50✔
249
    }
250
    return 0;
59✔
251
}
252

253
int Trick::DataRecordGroup::set_phase( unsigned short in_phase ) {
596✔
254
    write_job->phase = in_phase ;
596✔
255
    return(0) ;
596✔
256
}
257

258
int Trick::DataRecordGroup::set_freq( DR_Freq in_freq ) {
38✔
259
    freq = in_freq ;
38✔
260
    return(0) ;
38✔
261
}
262

263
int Trick::DataRecordGroup::set_max_buffer_size( int num ) {
×
264
    max_num = num ;
×
265
    return(0) ;
×
266
}
267

268
int Trick::DataRecordGroup::set_buffer_type( int in_buffer_type ) {
46✔
269
    buffer_type = (DR_Buffering)in_buffer_type ;
46✔
270
    return(0) ;
46✔
271
}
272

273
int Trick::DataRecordGroup::set_max_file_size( uint64_t bytes ) {
18✔
274
    if(bytes == 0) {
18✔
275
        max_file_size = UINT64_MAX ;
×
276
    } else {
277
    max_file_size = bytes ; 
18✔
278
    }
279
    return(0) ;
18✔
280
}
281

282
int Trick::DataRecordGroup::set_single_prec_only( bool in_single_prec_only ) {
26✔
283
    single_prec_only = in_single_prec_only ;
26✔
284
    return(0) ;
26✔
285
}
286

287
int Trick::DataRecordGroup::set_thread( unsigned int in_thread_id ) {
202✔
288

289
    unsigned int jj ;
290
    Trick::JobData * temp_job  ;
291

292
    /* make all data_record_groups have same sim object id as data_record */
293
    for ( jj = 0 ; jj < jobs.size() ; jj++ ) {
1,828✔
294
        temp_job = jobs[jj] ;
1,626✔
295
        temp_job->thread = in_thread_id ;
1,626✔
296
    }
297
    return 0 ;
202✔
298
}
299

300
int Trick::DataRecordGroup::set_job_class( std::string in_class ) {
596✔
301
    write_job->job_class_name = job_class = in_class ;
596✔
302
    return(0) ;
596✔
303
}
304

305
int Trick::DataRecordGroup::add_time_variable() {
658✔
306
    REF2 * new_ref ;
307

308
    // Create attributes for time recorded as a double
309
    time_value_attr.type = TRICK_DOUBLE ;
658✔
310
    time_value_attr.size = sizeof(double) ;
658✔
311
    time_value_attr.units = strdup("s") ;
658✔
312

313
    // Create a reference that records time as sys.exec.out.time
314
    new_ref = (REF2 *)calloc( 1 , sizeof(REF2));
658✔
315
    new_ref->reference = strdup("sys.exec.out.time") ;
658✔
316
    new_ref->address = &curr_time ;
658✔
317
    new_ref->attr = &time_value_attr ;
658✔
318
    add_variable(new_ref) ;
658✔
319

320
    return 0 ;
658✔
321
}
322

323
int Trick::DataRecordGroup::add_variable( std::string in_name , std::string alias ) {
964✔
324

325
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
964✔
326
    // Trim leading spaces
327
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
964✔
328
    // Trim trailing spaces
329
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
964✔
330
    new_var->name = in_name ;
964✔
331
    new_var->alias = alias ;
964✔
332
    rec_buffer.push_back(new_var) ;
964✔
333
    return 0 ;
964✔
334
}
335

336
void Trick::DataRecordGroup::remove_variable( std::string in_name ) {
×
337
    // Trim leading spaces++ 
338
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
×
339
    // Trim trailing spaces
340
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
×
341

342
    if (!in_name.compare("sys.exec.out.time")) {
×
343
        // This class assumes sim time is always the first variable.
344
        // Removing it results in errors.
345
        return;
×
346
    }
347

348
    auto remove_from = [&](std::vector<DataRecordBuffer*>& buffer) {
×
349
        for (auto i = buffer.begin(); i != buffer.end(); ++i) {
×
350
            if (!(*i)->name.compare(in_name)) {
×
351
                delete *i;
×
352
                buffer.erase(i);
×
353
                break;
×
354
            }
355
        }
356
    };
×
357

358
    remove_from(rec_buffer);
×
359
    remove_from(change_buffer);
×
360
}
361

362
void Trick::DataRecordGroup::remove_all_variables() {
55✔
363
    // remove all but the first variable, which is sim time
364
    if(!rec_buffer.empty()) {
55✔
365
        for (auto i = rec_buffer.begin() + 1; i != rec_buffer.end(); ++i) {
615✔
366
            delete *i;
560✔
367
        }
368
        rec_buffer.erase(rec_buffer.begin() + 1, rec_buffer.end());
55✔
369
    }
370

371
    // remove everything
372
    for (auto variable : change_buffer) {
61✔
373
        delete variable;
6✔
374
    }
375

376
    change_buffer.clear();
55✔
377
}
55✔
378

379
int Trick::DataRecordGroup::add_variable( REF2 * ref2 ) {
4,722✔
380

381
    if ( ref2 == NULL || ref2->attr == NULL ) {
4,722✔
382
        return(-1) ;
×
383
    }
384

385
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
4,722✔
386
    new_var->name = std::string(ref2->reference) ;
4,722✔
387
    new_var->ref_searched = true ;
4,722✔
388
    new_var->ref = ref2 ;
4,722✔
389
    new_var->last_value = (char *)calloc(1 , new_var->ref->attr->size) ;
4,722✔
390
    // Don't allocate space for the temp storage buffer until "init"
391
    rec_buffer.push_back(new_var) ;
4,722✔
392

393
    return(0) ;
4,722✔
394

395
}
396

397
int Trick::DataRecordGroup::add_change_variable( std::string in_name ) {
6✔
398

399
    REF2 * ref2 ;
400

401
    ref2 = ref_attributes(in_name.c_str()) ;
6✔
402

403
    if ( ref2 == NULL || ref2->attr == NULL ) {
6✔
404
        message_publish(MSG_WARNING, "Could not find Data Record change variable %s.\n", in_name.c_str()) ;
×
405
        return(-1) ;
×
406
    }
407

408
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
6✔
409
    new_var->ref = ref2 ;
6✔
410
    new_var->name = in_name;
6✔
411
    new_var->buffer = (char *)malloc(ref2->attr->size) ;
6✔
412
    new_var->last_value =  NULL ;
6✔
413
    memcpy(new_var->buffer , ref2->address , ref2->attr->size) ;
6✔
414
    change_buffer.push_back(new_var) ;
6✔
415

416
    return(0) ;
6✔
417

418
}
419

420
bool Trick::DataRecordGroup::isSupportedType(REF2 * ref2, std::string& message) {
548✔
421
    if (ref2->attr->type == TRICK_STRING || ref2->attr->type == TRICK_STL || ref2->attr->type == TRICK_STRUCTURED) {
548✔
422
        message = "Cannot Data Record variable " + std::string(ref2->reference) + " of unsupported type " + std::to_string(ref2->attr->type);
2✔
423
        return false;
2✔
424
    }
425
    
426
    // If this is an array and not a single value, don't record it
427
    if (ref2->num_index != ref2->attr->num_index) {
546✔
428
        message = "Cannot Data Record arrayed variable " + std::string(ref2->reference);
4✔
429
        return false;
4✔
430
    }
431

432
    return true;
542✔
433
}
434

435
/**
436
@details
437
-# The simulation output directory is retrieved from the CommandLineArguments
438
-# The log header file is created
439
   -# The endianness of the log file is written to the log header.
440
   -# The names of the parameters contained in the log file are written to the header.
441
-# Memory buffers are allocated to store simulation data
442
-# The DataRecordGroupObject (a derived SimObject) is added to the Scheduler.
443
*/
444
int Trick::DataRecordGroup::init(bool is_restart) {
58✔
445

446
    unsigned int jj ;
447
    int ret ;
448

449
    // reset counter here so we can "re-init" our recording
450
    buffer_num = writer_num = total_bytes_written = 0 ;
58✔
451

452
    output_dir = command_line_args_get_output_dir() ;
58✔
453
    /* this is the common part of the record file name, the format specific will add the correct suffix */
454
    file_name = output_dir + "/log_" + group_name ;
58✔
455

456
    pthread_mutex_init(&buffer_mutex, NULL);
58✔
457

458
    // Allocate recording space for time.
459
    rec_buffer[0]->buffer = (char *)calloc(max_num , rec_buffer[0]->ref->attr->size) ;
58✔
460
    rec_buffer[0]->last_value = (char *)calloc(1 , rec_buffer[0]->ref->attr->size) ;
58✔
461

462
    /* Loop through all variables looking up names.  Allocate recording space
463
       according to size of the variable */
464
    for (jj = 1; jj < rec_buffer.size() ; jj++) {
644✔
465
        Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
586✔
466
        if ( drb->ref_searched == false ) {
586✔
467
            REF2 * ref2 ;
468

469
            ref2 = ref_attributes(drb->name.c_str()) ;
548✔
470
            if ( ref2 == NULL || ref2->attr == NULL ) {
548✔
471
                message_publish(MSG_WARNING, "Could not find Data Record variable %s.\n", drb->name.c_str()) ;
×
472
                rec_buffer.erase(rec_buffer.begin() + jj--) ;
×
473
                delete drb ;
×
474
                continue ;
×
475
            } else {
476
                std::string message;
548✔
477
                if (!isSupportedType(ref2, message)) {
548✔
478
                    message_publish(MSG_WARNING, "%s\n", message.c_str()) ;
6✔
479
                    rec_buffer.erase(rec_buffer.begin() + jj--) ;
6✔
480
                    delete drb ;
6✔
481
                    continue ;
6✔
482
                } else {
483
                    drb->ref = ref2 ;
542✔
484
                }
485
            }
486
        }
487
        if ( drb->alias.compare("") ) {
580✔
488
            drb->ref->reference = strdup(drb->alias.c_str()) ;
16✔
489
        }
490
        drb->last_value = (char *)calloc(1 , drb->ref->attr->size) ;
580✔
491
        drb->buffer = (char *)calloc(max_num , drb->ref->attr->size) ;
580✔
492
        drb->ref_searched = true ;
580✔
493
    }
494

495
    write_header() ;
58✔
496

497
    // call format specific initialization to open destination and write header
498
    ret = format_specific_init() ;
58✔
499

500
    if(!is_restart)
58✔
501
    {
502
        if(!check_if_rates_are_valid())
49✔
503
        {
NEW
504
            disable();
×
NEW
505
            return (1);
×
506
        }
507
        long long curr_tics = exec_get_time_tics();
49✔
508
        write_job->next_tics = curr_tics;
49✔
509

510
        long long next_next_tics = calculate_next_logging_tic(write_job->next_tics);
49✔
511
        write_job->cycle_tics = next_next_tics - curr_tics;
49✔
512
        write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
49✔
513
    }
514

515
    // set the inited flag to true when all initialization is done
516
    if ( ret == 0 ) {
58✔
517
        inited = true ;
58✔
518
    }
519

520
    return(0) ;
58✔
521

522
}
523

524
void Trick::DataRecordGroup::configure_jobs(DR_Type type) {
616✔
525
    switch(type) {
616✔
526
    default:
53✔
527
        // run the restart job in phase 60001
528
        add_job(0, 5, (char *)"restart", NULL, 1.0, (char *)"restart", (char *)"TRK", 60001) ;
53✔
529

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

540
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
616✔
541
        break ;
616✔
542
    }
543
    write_job->set_system_job_class(true);
616✔
544
}
616✔
545

546
int Trick::DataRecordGroup::checkpoint() {
13✔
547
    unsigned int jj ;
548

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

559
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
60✔
560
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
47✔
561

562
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
47✔
563
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
47✔
564
        }
565
    }
566

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

576
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
577
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
578

579
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
580
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
581
        }
582
    }
583

584
    return 0 ;
13✔
585
}
586

587
void Trick::DataRecordGroup::clear_checkpoint_vars() {
55✔
588
    
589
    if ( variable_names ) {
55✔
590
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
84✔
591
            TMM_delete_var_a(variable_names[jj]);
62✔
592
        }
593
        TMM_delete_var_a(variable_names) ;
22✔
594
    }
595

596
    if ( variable_alias ) {
55✔
597
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
84✔
598
            TMM_delete_var_a(variable_alias[jj]);
62✔
599
        }
600
        TMM_delete_var_a(variable_alias) ;
22✔
601
    }
602

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

610
    if ( change_variable_alias ) {
55✔
611
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
612
            TMM_delete_var_a(change_variable_alias[jj]);
4✔
613
        }
614
        TMM_delete_var_a(change_variable_alias) ;
4✔
615
    }
616

617
    variable_names = NULL ;
55✔
618
    variable_alias = NULL ;
55✔
619
    change_variable_names = NULL ;
55✔
620
    change_variable_alias = NULL ;
55✔
621
    num_variable_names = 0 ;
55✔
622
    num_change_variable_names = 0 ;
55✔
623
}
55✔
624

625
int Trick::DataRecordGroup::restart() {
9✔
626
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
9✔
627

628
    /* delete the current rec_buffer */
629
    for ( drb_it = rec_buffer.begin() ; drb_it != rec_buffer.end() ; ++drb_it ) {
18✔
630
        delete *drb_it ;
9✔
631
    }
632
    rec_buffer.clear() ;
9✔
633
    /* Add back the time variable */
634
    add_time_variable() ;
9✔
635

636
    /* delete the current change_buffer contents */
637
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
9✔
638
        delete *drb_it ;
×
639
    }
640
    change_buffer.clear() ;
9✔
641

642
    unsigned int jj ;
643
    /* add the variable names listed in the checkpoint file */
644
    for ( jj = 0 ; jj < num_variable_names ; jj++ ) {
24✔
645
        add_variable( variable_names[jj] , variable_alias[jj] ) ;
15✔
646
    }
647
    for ( jj = 0 ; jj < num_change_variable_names ; jj++ ) {
11✔
648
        add_change_variable( change_variable_names[jj] ) ;
2✔
649
    }
650

651
    clear_checkpoint_vars() ;
9✔
652

653
    // set the write job class to what is in the checkpoint file.
654
    write_job->job_class_name = job_class ;
9✔
655

656
    // reset the sim_object name.
657
    name = std::string("data_record_group_") + group_name ;
9✔
658

659
    /* call init to open the recording file and look up variable name addresses */
660
    init(true) ;
9✔
661

662
    return 0 ;
9✔
663
}
664

665
int Trick::DataRecordGroup::write_header() {
58✔
666

667
    unsigned int jj ;
668
    std::string header_name ;
116✔
669
    std::fstream out_stream ;
116✔
670

671
    /*! create the header file used by the GUIs */
672
    header_name = output_dir + "/log_" + group_name + ".header" ;
58✔
673

674
    out_stream.open(header_name.c_str(), std::fstream::out ) ;
58✔
675
    if ( ! out_stream  ||  ! out_stream.good() ) {
58✔
676
        return -1;
×
677
    }
678

679
    /* Header file first line is created in format specific header */
680
    out_stream << "log_" << group_name ;
58✔
681

682
    format_specific_header(out_stream) ;
58✔
683

684
    /* Output the file name, variable size, units, and variable name
685
     * to the rest of recorded data header file.
686
     * (e.g. file_name  C_type  units  sim_name)
687
     * Note: "sys.exec.out.time" should be the first variable in the buffer.
688
     */
689
    for (jj = 0; jj < rec_buffer.size() ; jj++) {
696✔
690
        /*! recording single data item */
691
        out_stream << "log_" << group_name << "\t"
638✔
692
            << type_string(rec_buffer[jj]->ref->attr->type,
1,276✔
693
                           rec_buffer[jj]->ref->attr->size) << "\t"
638✔
694
            << std::setw(6) ;
1,276✔
695

696
        if ( rec_buffer[jj]->ref->attr->mods & TRICK_MODS_UNITSDASHDASH ) {
638✔
697
            out_stream << "--" ;
4✔
698
        } else {
699
            out_stream << rec_buffer[jj]->ref->attr->units ;
634✔
700
        }
701
        out_stream << "\t" << rec_buffer[jj]->ref->reference << std::endl ;
638✔
702
    }
703

704
    // Send all unwritten characters in the buffer to its output/file.
705
    out_stream.flush() ;
58✔
706
    out_stream.close() ;
58✔
707

708
    return(0) ;
58✔
709

710
}
711

712
int Trick::DataRecordGroup::data_record(double in_time) {
4,754,836✔
713

714
    unsigned int jj ;
715
    unsigned int buffer_offset ;
716
    Trick::DataRecordBuffer * drb ;
717
    bool change_detected = false ;
4,754,836✔
718

719
    curr_time_dr_job = in_time;
4,754,836✔
720

721
    //TODO: does not handle bitfields correctly!
722
    if ( record == true ) {
4,754,836✔
723
        if ( freq != DR_Always ) {
4,754,836✔
724
            for (jj = 0; jj < change_buffer.size() ; jj++) {
1,644✔
725
                drb = change_buffer[jj] ;
822✔
726
                REF2 * ref = drb->ref ;
822✔
727
                if ( ref->pointer_present == 1 ) {
822✔
728
                    ref->address = follow_address_path(ref) ;
×
729
                }
730
                if ( memcmp( drb->buffer , drb->ref->address , drb->ref->attr->size) ) {
822✔
731
                    change_detected = true ;
28✔
732
                    memcpy( drb->buffer , drb->ref->address , drb->ref->attr->size) ;
28✔
733
                }
734
            }
735

736
        }
737

738
        if ( freq == DR_Always || change_detected == true ) {
4,754,836✔
739

740
            // If this is not the ring buffer and
741
            // we are going to have trouble fitting 2 data sets then write the data now.
742
            if ( buffer_type != DR_Ring_Buffer ) {
4,754,042✔
743
                if ( buffer_num - writer_num >= (max_num - 2) ) {
4,494,842✔
744
                    write_data(true) ;
×
745
                }
746
            }
747

748
            curr_time = in_time ;
4,754,042✔
749

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

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

829
    long long curr_tics = (long long)round(in_time * Trick::JobData::time_tic_value);
4,754,836✔
830
    advance_log_tics_given_curr_tic(curr_tics);
4,754,836✔
831

832
    write_job->next_tics = calculate_next_logging_tic(curr_tics);
4,754,836✔
833
    write_job->cycle_tics = write_job->next_tics - curr_tics;
4,754,836✔
834
    write_job->cycle = (double)write_job->cycle_tics / Trick::JobData::time_tic_value;
4,754,836✔
835

836
    return(0) ;
4,754,836✔
837
}
838

839
bool Trick::DataRecordGroup::check_if_rates_are_valid()
49✔
840
{
841
    // long long curr_tics = exec_get_time_tics();
842
    long long tic_value = Trick::JobData::time_tic_value;
49✔
843
    bool areValid = true;
49✔
844

845
    for(size_t ii = 0; ii < logging_rates.size(); ++ii)
104✔
846
    {
847
        double logging_rate = logging_rates[ii].rate_in_seconds;
55✔
848
        int ret = check_if_rate_is_valid(logging_rate);
55✔
849
        if(ret != 0){
55✔
NEW
850
            emit_rate_error(ret, ii, logging_rate);
×
NEW
851
            areValid = false;
×
852
        }
853
    }
854
    return areValid;
49✔
855
}
856

857
void Trick::DataRecordGroup::emit_rate_error(int rate_err_code, size_t log_idx, double err_rate)
3✔
858
{
859
    long long tic_value = Trick::JobData::time_tic_value;
3✔
860
    if(rate_err_code == 1) {
3✔
861
        message_publish(
1✔
862
            MSG_ERROR,
863
            "DataRecordGroup ERROR: Cycle for %lu logging rate idx is less than time tic value. cycle = "
864
            "%16.12f, time_tic = %16.12f\n",
865
            log_idx,
866
            err_rate,
867
            tic_value);
868
    } else if(rate_err_code == 2)
2✔
869
    {
870
        long long cycle_tics = (long long)round(err_rate * tic_value);
2✔
871
        message_publish(MSG_ERROR,
2✔
872
                        "DataRecordGroup ERROR: Cycle for %lu logging rate idx cannot be exactly scheduled "
873
                        "with time tic value. "
874
                        "cycle = %16.12f, cycle_tics = %lld , time_tic = %16.12f\n",
875
                        log_idx,
876
                        err_rate,
877
                        cycle_tics,
878
                        1.0 / tic_value);
879
    }
880
}
3✔
881

882
 int Trick::DataRecordGroup::check_if_rate_is_valid(double test_rate)
67✔
883
 {
884
    long long tic_value = Trick::JobData::time_tic_value;
67✔
885
    int ret = 0;
67✔
886

887
    double logging_rate = test_rate;
67✔
888
    if(logging_rate < (1.0 / tic_value))
67✔
889
    {
890
        ret = 1;        
1✔
891
    } else {        
892
        /* Calculate the if the cycle_tics would be a whole number  */
893
        double test_rem = fmod(logging_rate * (double)tic_value, 1.0);
66✔
894

895
        if(test_rem > 0.001)
66✔
896
        {
897
            ret = 2;
2✔
898
        }
899
    }
900
    
901
    return ret;
67✔
902
 }
903

904

905
/**
906
 * Loop through the required logging rates and calculate the
907
 * next logging time in tics.
908
 * @return Next logging time in tics,
909
 */
910
long long Trick::DataRecordGroup::calculate_next_logging_tic(long long min_tic)
4,754,903✔
911
{
912
    long long ticOfCycleToProcess = std::numeric_limits<long long>::max();
4,754,903✔
913

914
    // Loop over all the logging rates. If the logging rate's next tic is equal to the min_tic, test against
915
    // that rate's next cycle from min. Find the smallest next tic 
916
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,790,545✔
917
    {
918
        long long logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,035,642✔
919

920
        if(logNextTic == min_tic)
22,035,642✔
921
        {
922
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
64✔
923
        }
924

925
        if(logNextTic < ticOfCycleToProcess)
22,035,642✔
926
        {
927
            ticOfCycleToProcess = logNextTic;
15,512,125✔
928
        }
929
    }
930

931
    return ticOfCycleToProcess;
4,754,903✔
932
}
933

934
/**
935
 * Loop through the required logging rates and advance the next cycle tics of matching rates
936
 * @param curr_tic_in - time in tics to match and advance the next cycle tic
937
 */
938
void Trick::DataRecordGroup::advance_log_tics_given_curr_tic(long long curr_tic_in)
4,754,845✔
939
{
940
    for(size_t cycleIndex = 0; cycleIndex < logging_rates.size(); ++cycleIndex)
26,790,414✔
941
    {
942
        long long & logNextTic = logging_rates[cycleIndex].next_cycle_in_tics;
22,035,569✔
943
        logNextTic = LoggingCycle::calc_next_tics_on_or_after_input_tic(curr_tic_in, logging_rates[cycleIndex].rate_in_tics);
22,035,569✔
944
        if(logNextTic <= curr_tic_in)
22,035,569✔
945
        {
946
            logNextTic += logging_rates[cycleIndex].rate_in_tics;
6,920,272✔
947
        }
948
    }
949
}
4,754,845✔
950

951
int Trick::DataRecordGroup::write_data(bool must_write) {
525,756✔
952

953
    unsigned int local_buffer_num ;
954
    unsigned int num_to_write ;
955
    unsigned int writer_offset ;
956

957
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
525,756✔
958

959
        // buffer_mutex is used in this one place to prevent forced calls of write_data
960
        // to not overwrite data being written by the asynchronous thread.
961
        pthread_mutex_lock(&buffer_mutex) ;
89,540✔
962

963
        local_buffer_num = buffer_num ;
89,540✔
964
        if ( (local_buffer_num - writer_num) > max_num ) {
89,540✔
UNCOV
965
            num_to_write = max_num ;
×
966
        } else {
967
            num_to_write = (local_buffer_num - writer_num) ;
89,540✔
968
        }
969
        writer_num = local_buffer_num - num_to_write ;
89,540✔
970

971
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
972
        while ( writer_num != local_buffer_num ) {
4,843,582✔
973

974
            writer_offset = writer_num % max_num ;
4,754,042✔
975
            //! keep record of bytes written to file. Default max is 1GB
976
            total_bytes_written += format_specific_write_data(writer_offset) ;
4,754,042✔
977
            writer_num++ ;
4,754,042✔
978

979
        }
980

981
        if(!max_size_warning && (total_bytes_written > max_file_size)) {
89,540✔
UNCOV
982
            std::cerr << "WARNING: Data record max file size " << (static_cast<double>(max_file_size))/(1<<20) << "MB reached.\n"
×
UNCOV
983
            "https://nasa.github.io/trick/documentation/simulation_capabilities/Data-Record#changing-the-max-file-size-of-a-data-record-group-ascii-and-binary-only" 
×
984
            << std::endl;
×
985
            max_size_warning = true;
×
986
        }
987

988
        pthread_mutex_unlock(&buffer_mutex) ;
89,540✔
989

990
    }
991

992
    return 0 ;
525,616✔
993
}
994

995
int Trick::DataRecordGroup::enable() {
38✔
996
    record = true ;
38✔
997
    return(0) ;
38✔
998
}
999

UNCOV
1000
int Trick::DataRecordGroup::disable() {
×
UNCOV
1001
    record = false ;
×
1002
    return(0) ;
×
1003
}
1004

1005
int Trick::DataRecordGroup::shutdown() {
55✔
1006

1007
    // Force write out all data
1008
    record = true ; // If user disabled group, make sure any recorded data gets written out
55✔
1009
    write_data(true) ;
55✔
1010
    format_specific_shutdown() ;
55✔
1011

1012
    remove_all_variables();
55✔
1013

1014
    // remove_all_variables does not remove sim time
1015
    if(!rec_buffer.empty()){
55✔
1016
        delete rec_buffer[0];
55✔
1017
        rec_buffer.clear();
55✔
1018
    }
1019

1020
    if ( writer_buff ) {
55✔
1021
        free(writer_buff) ;
55✔
1022
        writer_buff = NULL ;
55✔
1023
    }
1024

1025
    return 0 ;
55✔
1026
}
1027

1028
std::string Trick::DataRecordGroup::type_string( int item_type, int item_size ) {
638✔
1029
    switch (item_type) {
638✔
1030
        case TRICK_CHARACTER:
2✔
1031
                return "char";
2✔
1032
                break;
1033
        case TRICK_UNSIGNED_CHARACTER:
2✔
1034
                return "unsigned_char";
2✔
1035
                break;
UNCOV
1036
        case TRICK_STRING:
×
UNCOV
1037
                return "string";
×
1038
                break;
1039
        case TRICK_SHORT:
5✔
1040
                return "short";
5✔
1041
                break;
1042
        case TRICK_UNSIGNED_SHORT:
5✔
1043
                return "unsigned_short";
5✔
1044
                break;
1045
        case TRICK_ENUMERATED:
77✔
1046
        case TRICK_INTEGER:
1047
                return "int";
77✔
1048
                break;
1049
        case TRICK_UNSIGNED_INTEGER:
5✔
1050
                return "unsigned_int";
5✔
1051
                break;
1052
        case TRICK_LONG:
5✔
1053
                return "long";
5✔
1054
                break;
1055
        case TRICK_UNSIGNED_LONG:
5✔
1056
                return "unsigned_long";
5✔
1057
                break;
1058
        case TRICK_FLOAT:
5✔
1059
                return "float";
5✔
1060
                break;
1061
        case TRICK_DOUBLE:
460✔
1062
                if ( single_prec_only ) {
460✔
UNCOV
1063
                        return "float";
×
1064
                }
1065
                else {
1066
                        return "double";
460✔
1067
                }
1068
                break;
1069
        case TRICK_BITFIELD:
28✔
1070
                if (item_size == sizeof(int)) {
28✔
1071
                        return "int";
28✔
UNCOV
1072
                } else if (item_size == sizeof(short)) {
×
UNCOV
1073
                        return "short";
×
1074
                } else {
1075
                        return "char";
×
1076
                }
1077
                break;
1078
        case TRICK_UNSIGNED_BITFIELD:
28✔
1079
                if (item_size == sizeof(int)) {
28✔
1080
                        return "unsigned_int";
28✔
UNCOV
1081
                } else if (item_size == sizeof(short)) {
×
UNCOV
1082
                        return "unsigned_short";
×
1083
                } else {
1084
                        return "unsigned_char";
×
1085
                }
1086
                break;
1087
        case TRICK_LONG_LONG:
5✔
1088
                return "long_long";
5✔
1089
                break;
1090
        case TRICK_UNSIGNED_LONG_LONG:
2✔
1091
                return "unsigned_long_long";
2✔
1092
                break;
1093
        case TRICK_BOOLEAN:
4✔
1094
#if ( __sun | __APPLE__ )
1095
                return "int";
1096
#else
1097
                return "unsigned_char";
4✔
1098
#endif
1099
                break;
1100
    }
UNCOV
1101
    return "";
×
1102
}
1103

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