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

nasa / trick / 16842923291

09 Aug 2025 12:05AM UTC coverage: 55.87% (-0.03%) from 55.895%
16842923291

Pull #1929

github

web-flow
Merge a431e5dda into a10cb344f
Pull Request #1929: Updated to allow to use index [0] on regular pointer.

3 of 36 new or added lines in 2 files covered. (8.33%)

44 existing lines in 5 files now uncovered.

12345 of 22096 relevant lines covered (55.87%)

268509.43 hits per line

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

81.88
/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

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

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

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

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

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

45
Trick::DataRecordBuffer::~DataRecordBuffer() {
620✔
46
    if ( buffer ) {
620✔
47
        free(buffer) ;
607✔
48
    }
49
    if ( last_value ) {
620✔
50
        free(last_value) ;
608✔
51
    }
52

53
    ref_free(ref) ;
620✔
54
    free(ref) ;
620✔
55
}
620✔
56

57
Trick::DataRecordGroup::DataRecordGroup( std::string in_name, Trick::DR_Type dr_type ) :
598✔
58
 record(true) ,
59
 inited(false) ,
60
 group_name(in_name) ,
61
 freq(DR_Always),
62
 start(0.0) ,
63
 cycle(0.1) ,
64
 time_value_attr() ,
65
 num_variable_names(0),
66
 variable_names(NULL),
67
 variable_alias(NULL),
68
 num_change_variable_names(0),
69
 change_variable_names(NULL),
70
 change_variable_alias(NULL),
71
 max_num(100000),
72
 buffer_num(0),
73
 writer_num(0),
74
 max_file_size(1<<30), // 1 GB
75
 total_bytes_written(0),
76
 max_size_warning(false),
77
 writer_buff(NULL),
78
 single_prec_only(false),
79
 buffer_type(DR_Buffer),
80
 job_class("data_record"),
81
 curr_time(0.0)
598✔
82
{
83

84
    union {
85
        long l;
86
        char c[sizeof(long)];
87
    } byte_order_union;
88

89
    byte_order_union.l = 1;
598✔
90
    if (byte_order_union.c[sizeof(long) - 1] != 1) {
598✔
91
        byte_order = "little_endian" ;
598✔
92
    } else {
93
        byte_order = "big_endian" ;
×
94
    }
95

96
    // sim object name
97
    name = std::string("trick_data_record_group_") + in_name ;
598✔
98

99
    configure_jobs(dr_type) ;
598✔
100

101
    add_time_variable() ;
598✔
102
}
598✔
103

104
Trick::DataRecordGroup::~DataRecordGroup() {
40✔
105
    free((void *)time_value_attr.units) ;
40✔
106
}
40✔
107

108
int Trick::DataRecordGroup::call_function( Trick::JobData * curr_job ) {
804,548✔
109

110
    int ret = 0 ;
804,548✔
111

112
    switch (curr_job->id) {
804,548✔
113
        case 1:
42✔
114
            ret = init() ;
42✔
115
            break ;
42✔
116
        case 2:
365,030✔
117
            ret = write_data(false) ;
365,030✔
118
            break ;
365,030✔
119
        case 3:
11✔
120
            ret = checkpoint() ;
11✔
121
            break ;
11✔
122
        case 4:
11✔
123
            clear_checkpoint_vars() ;
11✔
124
            break ;
11✔
125
        case 5:
7✔
126
            ret = restart() ;
7✔
127
            break ;
7✔
128
        case 6:
42✔
129
            ret = shutdown() ;
42✔
130
            break ;
42✔
131
        default:
439,405✔
132
            ret = data_record(exec_get_sim_time()) ;
439,405✔
133
            break ;
439,405✔
134
    }
135

136
    return(ret) ;
804,548✔
137

138
}
139

140
double Trick::DataRecordGroup::call_function_double( Trick::JobData * curr_job ) {
×
141
    (void) curr_job ;
142
    return(0.0) ;
×
143
}
144

145
void Trick::DataRecordGroup::register_group_with_mm(void * address , const char * type) {
40✔
146
    // Only add to the memory manager if it has not already been added
147
    if ( TMM_var_exists(name.c_str()) == 0 ) {
40✔
148
        // Declare this to the memory manager.  Must be done here to get the correct type name
149
        TMM_declare_ext_var(address , TRICK_STRUCTURED, type , 0 , name.c_str() , 0 , NULL ) ;
40✔
150
        ALLOC_INFO * alloc_info = get_alloc_info_at(address) ;
40✔
151
        alloc_info->stcl = TRICK_LOCAL ;
40✔
152
        alloc_info->alloc_type = TRICK_ALLOC_NEW ;
40✔
153
    }
154
}
40✔
155

156
const std::string & Trick::DataRecordGroup::get_group_name() {
24✔
157
    return group_name ;
24✔
158
}
159

160
int Trick::DataRecordGroup::set_cycle( double in_cycle ) {
38✔
161
    write_job->set_cycle(in_cycle) ;
38✔
162
    return(0) ;
38✔
163
}
164

165
int Trick::DataRecordGroup::set_phase( unsigned short in_phase ) {
578✔
166
    write_job->phase = in_phase ;
578✔
167
    return(0) ;
578✔
168
}
169

170
int Trick::DataRecordGroup::set_freq( DR_Freq in_freq ) {
34✔
171
    freq = in_freq ;
34✔
172
    return(0) ;
34✔
173
}
174

175
int Trick::DataRecordGroup::set_max_buffer_size( int num ) {
×
176
    max_num = num ;
×
177
    return(0) ;
×
178
}
179

180
int Trick::DataRecordGroup::set_buffer_type( int in_buffer_type ) {
42✔
181
    buffer_type = (DR_Buffering)in_buffer_type ;
42✔
182
    return(0) ;
42✔
183
}
184

185
int Trick::DataRecordGroup::set_max_file_size( uint64_t bytes ) {
14✔
186
    if(bytes == 0) {
14✔
187
        max_file_size = UINT64_MAX ;
×
188
    } else {
189
    max_file_size = bytes ; 
14✔
190
    }
191
    return(0) ;
14✔
192
}
193

194
int Trick::DataRecordGroup::set_single_prec_only( bool in_single_prec_only ) {
22✔
195
    single_prec_only = in_single_prec_only ;
22✔
196
    return(0) ;
22✔
197
}
198

199
int Trick::DataRecordGroup::set_thread( unsigned int in_thread_id ) {
196✔
200

201
    unsigned int jj ;
202
    Trick::JobData * temp_job  ;
203

204
    /* make all data_record_groups have same sim object id as data_record */
205
    for ( jj = 0 ; jj < jobs.size() ; jj++ ) {
1,774✔
206
        temp_job = jobs[jj] ;
1,578✔
207
        temp_job->thread = in_thread_id ;
1,578✔
208
    }
209
    return 0 ;
196✔
210
}
211

212
int Trick::DataRecordGroup::set_job_class( std::string in_class ) {
578✔
213
    write_job->job_class_name = job_class = in_class ;
578✔
214
    return(0) ;
578✔
215
}
216

217
int Trick::DataRecordGroup::add_time_variable() {
632✔
218
    REF2 * new_ref ;
219

220
    // Create attributes for time recorded as a double
221
    time_value_attr.type = TRICK_DOUBLE ;
632✔
222
    time_value_attr.size = sizeof(double) ;
632✔
223
    time_value_attr.units = strdup("s") ;
632✔
224

225
    // Create a reference that records time as sys.exec.out.time
226
    new_ref = (REF2 *)calloc( 1 , sizeof(REF2));
632✔
227
    new_ref->reference = strdup("sys.exec.out.time") ;
632✔
228
    new_ref->address = &curr_time ;
632✔
229
    new_ref->attr = &time_value_attr ;
632✔
230
    add_variable(new_ref) ;
632✔
231

232
    return 0 ;
632✔
233
}
234

235
int Trick::DataRecordGroup::add_variable( std::string in_name , std::string alias ) {
944✔
236

237
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
944✔
238
    // Trim leading spaces
239
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
944✔
240
    // Trim trailing spaces
241
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
944✔
242
    new_var->name = in_name ;
944✔
243
    new_var->alias = alias ;
944✔
244
    rec_buffer.push_back(new_var) ;
944✔
245
    return 0 ;
944✔
246
}
247

248
void Trick::DataRecordGroup::remove_variable( std::string in_name ) {
×
249
    // Trim leading spaces++ 
250
    in_name.erase( 0, in_name.find_first_not_of( " \t" ) );
×
251
    // Trim trailing spaces
252
    in_name.erase( in_name.find_last_not_of( " \t" ) + 1);
×
253

254
    if (!in_name.compare("sys.exec.out.time")) {
×
255
        // This class assumes sim time is always the first variable.
256
        // Removing it results in errors.
257
        return;
×
258
    }
259

260
    auto remove_from = [&](std::vector<DataRecordBuffer*>& buffer) {
×
261
        for (auto i = buffer.begin(); i != buffer.end(); ++i) {
×
262
            if (!(*i)->name.compare(in_name)) {
×
263
                delete *i;
×
264
                buffer.erase(i);
×
265
                break;
×
266
            }
267
        }
268
    };
×
269

270
    remove_from(rec_buffer);
×
271
    remove_from(change_buffer);
×
272
}
273

274
void Trick::DataRecordGroup::remove_all_variables() {
49✔
275
    // remove all but the first variable, which is sim time
276
    if(!rec_buffer.empty()) {
49✔
277
        for (auto i = rec_buffer.begin() + 1; i != rec_buffer.end(); ++i) {
601✔
278
            delete *i;
552✔
279
        }
280
        rec_buffer.erase(rec_buffer.begin() + 1, rec_buffer.end());
49✔
281
    }
282

283
    // remove everything
284
    for (auto variable : change_buffer) {
55✔
285
        delete variable;
6✔
286
    }
287

288
    change_buffer.clear();
49✔
289
}
49✔
290

291
int Trick::DataRecordGroup::add_variable( REF2 * ref2 ) {
4,572✔
292

293
    if ( ref2 == NULL || ref2->attr == NULL ) {
4,572✔
294
        return(-1) ;
×
295
    }
296

297
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
4,572✔
298
    new_var->name = std::string(ref2->reference) ;
4,572✔
299
    new_var->ref_searched = true ;
4,572✔
300
    new_var->ref = ref2 ;
4,572✔
301
    new_var->last_value = (char *)calloc(1 , new_var->ref->attr->size) ;
4,572✔
302
    // Don't allocate space for the temp storage buffer until "init"
303
    rec_buffer.push_back(new_var) ;
4,572✔
304

305
    return(0) ;
4,572✔
306

307
}
308

309
int Trick::DataRecordGroup::add_change_variable( std::string in_name ) {
6✔
310

311
    REF2 * ref2 ;
312

313
    ref2 = ref_attributes(in_name.c_str()) ;
6✔
314

315
    if ( ref2 == NULL || ref2->attr == NULL ) {
6✔
316
        message_publish(MSG_WARNING, "Could not find Data Record change variable %s.\n", in_name.c_str()) ;
×
317
        return(-1) ;
×
318
    }
319

320
    Trick::DataRecordBuffer * new_var = new Trick::DataRecordBuffer ;
6✔
321
    new_var->ref = ref2 ;
6✔
322
    new_var->name = in_name;
6✔
323
    new_var->buffer = (char *)malloc(ref2->attr->size) ;
6✔
324
    new_var->last_value =  NULL ;
6✔
325
    memcpy(new_var->buffer , ref2->address , ref2->attr->size) ;
6✔
326
    change_buffer.push_back(new_var) ;
6✔
327

328
    return(0) ;
6✔
329

330
}
331

332
bool Trick::DataRecordGroup::isSupportedType(REF2 * ref2, std::string& message) {
540✔
333
    if (ref2->attr->type == TRICK_STRING || ref2->attr->type == TRICK_STL || ref2->attr->type == TRICK_STRUCTURED) {
540✔
334
        message = "Cannot Data Record variable " + std::string(ref2->reference) + " of unsupported type " + std::to_string(ref2->attr->type);
2✔
335
        return false;
2✔
336
    }
337
    
338
    // If this is an array and not a single value, don't record it
339
    if (ref2->num_index != ref2->attr->num_index) {
538✔
340
        message = "Cannot Data Record arrayed variable " + std::string(ref2->reference);
4✔
341
        return false;
4✔
342
    }
343

344
    return true;
534✔
345
}
346

347
/**
348
@details
349
-# The simulation output directory is retrieved from the CommandLineArguments
350
-# The log header file is created
351
   -# The endianness of the log file is written to the log header.
352
   -# The names of the parameters contained in the log file are written to the header.
353
-# Memory buffers are allocated to store simulation data
354
-# The DataRecordGroupObject (a derived SimObject) is added to the Scheduler.
355
*/
356
int Trick::DataRecordGroup::init() {
52✔
357

358
    unsigned int jj ;
359
    int ret ;
360

361
    // reset counter here so we can "re-init" our recording
362
    buffer_num = writer_num = total_bytes_written = 0 ;
52✔
363

364
    output_dir = command_line_args_get_output_dir() ;
52✔
365
    /* this is the common part of the record file name, the format specific will add the correct suffix */
366
    file_name = output_dir + "/log_" + group_name ;
52✔
367

368
    pthread_mutex_init(&buffer_mutex, NULL);
52✔
369

370
    // Allocate recording space for time.
371
    rec_buffer[0]->buffer = (char *)calloc(max_num , rec_buffer[0]->ref->attr->size) ;
52✔
372
    rec_buffer[0]->last_value = (char *)calloc(1 , rec_buffer[0]->ref->attr->size) ;
52✔
373

374
    /* Loop through all variables looking up names.  Allocate recording space
375
       according to size of the variable */
376
    for (jj = 1; jj < rec_buffer.size() ; jj++) {
630✔
377
        Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
578✔
378
        if ( drb->ref_searched == false ) {
578✔
379
            REF2 * ref2 ;
380

381
            ref2 = ref_attributes(drb->name.c_str()) ;
540✔
382
            if ( ref2 == NULL || ref2->attr == NULL ) {
540✔
383
                message_publish(MSG_WARNING, "Could not find Data Record variable %s.\n", drb->name.c_str()) ;
×
384
                rec_buffer.erase(rec_buffer.begin() + jj--) ;
×
385
                delete drb ;
×
386
                continue ;
×
387
            } else {
388
                std::string message;
540✔
389
                if (!isSupportedType(ref2, message)) {
540✔
390
                    message_publish(MSG_WARNING, "%s\n", message.c_str()) ;
6✔
391
                    rec_buffer.erase(rec_buffer.begin() + jj--) ;
6✔
392
                    delete drb ;
6✔
393
                    continue ;
6✔
394
                } else {
395
                    drb->ref = ref2 ;
534✔
396
                }
397
            }
398
        }
399
        if ( drb->alias.compare("") ) {
572✔
400
            drb->ref->reference = strdup(drb->alias.c_str()) ;
16✔
401
        }
402
        drb->last_value = (char *)calloc(1 , drb->ref->attr->size) ;
572✔
403
        drb->buffer = (char *)calloc(max_num , drb->ref->attr->size) ;
572✔
404
        drb->ref_searched = true ;
572✔
405
    }
406

407
    write_header() ;
52✔
408

409
    // call format specific initialization to open destination and write header
410
    ret = format_specific_init() ;
52✔
411

412
    // set the inited flag to true when all initialization is done
413
    if ( ret == 0 ) {
52✔
414
        inited = true ;
52✔
415
    }
416

417
    return(0) ;
52✔
418

419
}
420

421
void Trick::DataRecordGroup::configure_jobs(DR_Type type) {
598✔
422
    switch(type) {
598✔
423
    default:
47✔
424
        // run the restart job in phase 60001
425
        add_job(0, 5, (char *)"restart", NULL, 1.0, (char *)"restart", (char *)"TRK", 60001) ;
47✔
426

427
    case DR_Type::DR_Type_FrameLogDataRecord:
598✔
428
        // add_jobs_to_queue will fill in job_id later
429
        // make the init job run after all other initialization jobs but before the post init checkpoint
430
        // job so users can allocate memory in initialization jobs and checkpointing data rec groups will work
431
        add_job(0, 1, (char *)"initialization", NULL, cycle, (char *)"init", (char *)"TRK", 65534) ;
598✔
432
        add_job(0, 2, (char *)"end_of_frame", NULL, 1.0, (char *)"write_data", (char *)"TRK") ;
598✔
433
        add_job(0, 3, (char *)"checkpoint", NULL, 1.0, (char *)"checkpoint", (char *)"TRK") ;
598✔
434
        add_job(0, 4, (char *)"post_checkpoint", NULL, 1.0, (char *)"clear_checkpoint_vars", (char *)"TRK") ;
598✔
435
        add_job(0, 6, (char *)"shutdown", NULL, 1.0, (char *)"shutdown", (char *)"TRK") ;
598✔
436

437
        write_job = add_job(0, 99, (char *)job_class.c_str(), NULL, cycle, (char *)"data_record" , (char *)"TRK") ;
598✔
438
        break ;
598✔
439
    }
440
}
598✔
441

442
int Trick::DataRecordGroup::checkpoint() {
11✔
443
    unsigned int jj ;
444

445
    /*
446
       Save the names of the variables and the aliases to the checkpoint,
447
       the rest of the DataRecordBuffer will be reconstructed during restart.
448
       The first variable is time which we do not have to save.
449
     */
450
    if ( rec_buffer.size() > 1 ) {
11✔
451
        num_variable_names = rec_buffer.size() - 1 ;
11✔
452
        variable_names = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
11✔
453
        variable_alias = (char **)TMM_declare_var_1d("char *", (int)rec_buffer.size() - 1) ;
11✔
454

455
        for (jj = 1; jj < rec_buffer.size() ; jj++) {
56✔
456
            Trick::DataRecordBuffer * drb = rec_buffer[jj] ;
45✔
457

458
            variable_names[jj-1] = TMM_strdup((char *)drb->name.c_str()) ;
45✔
459
            variable_alias[jj-1] = TMM_strdup((char *)drb->alias.c_str()) ;
45✔
460
        }
461
    }
462

463
    /*
464
       Save the names of the change variables and the aliases to the checkpoint,
465
       the rest of the DataRecordBuffer will be reconstructed during restart
466
     */
467
    if ( change_buffer.size() > 0 ) {
11✔
468
        num_change_variable_names = change_buffer.size() ;
2✔
469
        change_variable_names = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
470
        change_variable_alias = (char **)TMM_declare_var_1d("char *", (int)change_buffer.size()) ;
2✔
471

472
        for (jj = 0; jj < change_buffer.size() ; jj++) {
4✔
473
            Trick::DataRecordBuffer * drb = change_buffer[jj] ;
2✔
474

475
            change_variable_names[jj] = TMM_strdup((char *)drb->name.c_str()) ;
2✔
476
            change_variable_alias[jj] = TMM_strdup((char *)drb->alias.c_str()) ;
2✔
477
        }
478
    }
479

480
    return 0 ;
11✔
481
}
482

483
void Trick::DataRecordGroup::clear_checkpoint_vars() {
45✔
484
    
485
    if ( variable_names ) {
45✔
486
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
76✔
487
            TMM_delete_var_a(variable_names[jj]);
58✔
488
        }
489
        TMM_delete_var_a(variable_names) ;
18✔
490
    }
491

492
    if ( variable_alias ) {
45✔
493
        for(unsigned int jj = 0; jj < num_variable_names; jj++) {
76✔
494
            TMM_delete_var_a(variable_alias[jj]);
58✔
495
        }
496
        TMM_delete_var_a(variable_alias) ;
18✔
497
    }
498

499
    if ( change_variable_names ) {
45✔
500
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
501
            TMM_delete_var_a(change_variable_names[jj]);
4✔
502
        }
503
        TMM_delete_var_a(change_variable_names) ;
4✔
504
    }
505

506
    if ( change_variable_alias ) {
45✔
507
        for(unsigned int jj = 0; jj < num_change_variable_names; jj++) {
8✔
508
            TMM_delete_var_a(change_variable_alias[jj]);
4✔
509
        }
510
        TMM_delete_var_a(change_variable_alias) ;
4✔
511
    }
512

513
    variable_names = NULL ;
45✔
514
    variable_alias = NULL ;
45✔
515
    change_variable_names = NULL ;
45✔
516
    change_variable_alias = NULL ;
45✔
517
    num_variable_names = 0 ;
45✔
518
    num_change_variable_names = 0 ;
45✔
519
}
45✔
520

521
int Trick::DataRecordGroup::restart() {
7✔
522
    std::vector <Trick::DataRecordBuffer *>::iterator drb_it ;
7✔
523

524
    /* delete the current rec_buffer */
525
    for ( drb_it = rec_buffer.begin() ; drb_it != rec_buffer.end() ; ++drb_it ) {
14✔
526
        delete *drb_it ;
7✔
527
    }
528
    rec_buffer.clear() ;
7✔
529
    /* Add back the time variable */
530
    add_time_variable() ;
7✔
531

532
    /* delete the current change_buffer contents */
533
    for ( drb_it = change_buffer.begin() ; drb_it != change_buffer.end() ; ++drb_it ) {
7✔
534
        delete *drb_it ;
×
535
    }
536
    change_buffer.clear() ;
7✔
537

538
    unsigned int jj ;
539
    /* add the variable names listed in the checkpoint file */
540
    for ( jj = 0 ; jj < num_variable_names ; jj++ ) {
20✔
541
        add_variable( variable_names[jj] , variable_alias[jj] ) ;
13✔
542
    }
543
    for ( jj = 0 ; jj < num_change_variable_names ; jj++ ) {
9✔
544
        add_change_variable( change_variable_names[jj] ) ;
2✔
545
    }
546

547
    clear_checkpoint_vars() ;
7✔
548

549
    // set the write job class to what is in the checkpoint file.
550
    write_job->job_class_name = job_class ;
7✔
551

552
    // reset the sim_object name.
553
    name = std::string("data_record_group_") + group_name ;
7✔
554

555
    /* call init to open the recording file and look up variable name addresses */
556
    init() ;
7✔
557

558
    return 0 ;
7✔
559
}
560

561
int Trick::DataRecordGroup::write_header() {
52✔
562

563
    unsigned int jj ;
564
    std::string header_name ;
104✔
565
    std::fstream out_stream ;
104✔
566

567
    /*! create the header file used by the GUIs */
568
    header_name = output_dir + "/log_" + group_name + ".header" ;
52✔
569

570
    out_stream.open(header_name.c_str(), std::fstream::out ) ;
52✔
571
    if ( ! out_stream  ||  ! out_stream.good() ) {
52✔
572
        return -1;
×
573
    }
574

575
    /* Header file first line is created in format specific header */
576
    out_stream << "log_" << group_name ;
52✔
577

578
    format_specific_header(out_stream) ;
52✔
579

580
    /* Output the file name, variable size, units, and variable name
581
     * to the rest of recorded data header file.
582
     * (e.g. file_name  C_type  units  sim_name)
583
     * Note: "sys.exec.out.time" should be the first variable in the buffer.
584
     */
585
    for (jj = 0; jj < rec_buffer.size() ; jj++) {
676✔
586
        /*! recording single data item */
587
        out_stream << "log_" << group_name << "\t"
624✔
588
            << type_string(rec_buffer[jj]->ref->attr->type,
1,248✔
589
                           rec_buffer[jj]->ref->attr->size) << "\t"
624✔
590
            << std::setw(6) ;
1,248✔
591

592
        if ( rec_buffer[jj]->ref->attr->mods & TRICK_MODS_UNITSDASHDASH ) {
624✔
593
            out_stream << "--" ;
4✔
594
        } else {
595
            out_stream << rec_buffer[jj]->ref->attr->units ;
620✔
596
        }
597
        out_stream << "\t" << rec_buffer[jj]->ref->reference << std::endl ;
624✔
598
    }
599

600
    // Send all unwritten characters in the buffer to its output/file.
601
    out_stream.flush() ;
52✔
602
    out_stream.close() ;
52✔
603

604
    return(0) ;
52✔
605

606
}
607

608
int Trick::DataRecordGroup::data_record(double in_time) {
439,405✔
609

610
    unsigned int jj ;
611
    unsigned int buffer_offset ;
612
    Trick::DataRecordBuffer * drb ;
613
    bool change_detected = false ;
439,405✔
614

615
    //TODO: does not handle bitfields correctly!
616
    if ( record == true ) {
439,405✔
617
        if ( freq != DR_Always ) {
439,405✔
618
            for (jj = 0; jj < change_buffer.size() ; jj++) {
1,644✔
619
                drb = change_buffer[jj] ;
822✔
620
                REF2 * ref = drb->ref ;
822✔
621
                if ( ref->pointer_present == 1 ) {
822✔
622
                    ref->address = follow_address_path(ref) ;
×
623
                }
624
                if ( memcmp( drb->buffer , drb->ref->address , drb->ref->attr->size) ) {
822✔
625
                    change_detected = true ;
28✔
626
                    memcpy( drb->buffer , drb->ref->address , drb->ref->attr->size) ;
28✔
627
                }
628
            }
629

630
        }
631

632
        if ( freq == DR_Always || change_detected == true ) {
439,405✔
633

634
            // If this is not the ring buffer and
635
            // we are going to have trouble fitting 2 data sets then write the data now.
636
            if ( buffer_type != DR_Ring_Buffer ) {
438,611✔
637
                if ( buffer_num - writer_num >= (max_num - 2) ) {
179,411✔
638
                    write_data(true) ;
×
639
                }
640
            }
641

642
            curr_time = in_time ;
438,611✔
643

644
            if ( freq == DR_Changes_Step ) {
438,611✔
645
                buffer_offset = buffer_num % max_num ;
×
646
                *((double *)(rec_buffer[0]->last_value)) = in_time ;
×
647
                for (jj = 0; jj < rec_buffer.size() ; jj++) {
×
648
                    drb = rec_buffer[jj] ;
×
649
                    REF2 * ref = drb->ref ;
×
650
                    int param_size = ref->attr->size ;
×
651
                    if ( buffer_offset == 0 ) {
×
652
                       drb->curr_buffer = drb->buffer ;
×
653
                    } else {
654
                       drb->curr_buffer += param_size ;
×
655
                    }
656
                    switch ( param_size ) {
×
657
                        case 8:
×
658
                            *(int64_t *)drb->curr_buffer = *(int64_t *)drb->last_value ;
×
659
                            break ;
×
660
                        case 4:
×
661
                            *(int32_t *)drb->curr_buffer = *(int32_t *)drb->last_value ;
×
662
                            break ;
×
663
                        case 2:
×
664
                            *(int16_t *)drb->curr_buffer = *(int16_t *)drb->last_value ;
×
665
                            break ;
×
666
                        case 1:
×
667
                            *(int8_t *)drb->curr_buffer = *(int8_t *)drb->last_value ;
×
668
                            break ;
×
669
                        default:
×
670
                            memcpy( drb->curr_buffer , drb->last_value , param_size ) ;
×
671
                            break ;
×
672
                    }
673
                }
674
                buffer_num++ ;
×
675
            }
676

677
            buffer_offset = buffer_num % max_num ;
438,611✔
678
            for (jj = 0; jj < rec_buffer.size() ; jj++) {
5,136,036✔
679
                drb = rec_buffer[jj] ;
4,697,425✔
680
                REF2 * ref = drb->ref ;
4,697,425✔
681
                if ( ref->pointer_present == 1 ) {
4,697,425✔
682
                    ref->address = follow_address_path(ref) ;
×
683
                }
684
                int param_size = ref->attr->size ;
4,697,425✔
685
                if ( buffer_offset == 0 ) {
4,697,425✔
686
                   drb->curr_buffer = drb->buffer ;
601✔
687
                } else {
688
                   drb->curr_buffer += param_size ;
4,696,824✔
689
                }
690
                /**
691
                 * While the typical idiom is something like:
692
                 * 1. previous_value = current_value
693
                 * 2. current_value = new_value
694
                 * That is incorrect here, as curr_buffer is a pointer that has already been
695
                 * incremented to the next value's location. We therefore set *curr_buffer and
696
                 * *last_value to the same value, which results in the DR_Changes_Step loop above
697
                 * correctly using this value as the first point of the step change on the next
698
                 * call to this function.
699
                 */
700
                switch ( param_size ) {
4,697,425✔
701
                    case 8:
3,742,716✔
702
                        *(int64_t *)drb->last_value = *(int64_t *)drb->curr_buffer = *(int64_t *)ref->address ;
3,742,716✔
703
                        break ;
3,742,716✔
704
                    case 4:
953,989✔
705
                        *(int32_t *)drb->last_value = *(int32_t *)drb->curr_buffer = *(int32_t *)ref->address ;
953,989✔
706
                        break ;
953,989✔
707
                    case 2:
650✔
708
                        *(int16_t *)drb->last_value = *(int16_t *)drb->curr_buffer = *(int16_t *)ref->address ;
650✔
709
                        break ;
650✔
710
                    case 1:
70✔
711
                        *(int8_t *)drb->last_value = *(int8_t *)drb->curr_buffer = *(int8_t *)ref->address ;
70✔
712
                        break ;
70✔
UNCOV
713
                    default:
×
UNCOV
714
                        memcpy( drb->curr_buffer , ref->address , param_size ) ;
×
UNCOV
715
                        memcpy( drb->last_value , drb->curr_buffer , param_size ) ;
×
UNCOV
716
                        break ;
×
717
                }
718
            }
719
            buffer_num++ ;
438,611✔
720
        }
721
    }
722

723
    return(0) ;
439,405✔
724

725
}
726

727
int Trick::DataRecordGroup::write_data(bool must_write) {
410,143✔
728

729
    unsigned int local_buffer_num ;
730
    unsigned int num_to_write ;
731
    unsigned int writer_offset ;
732

733
    if ( record and inited and (buffer_type == DR_No_Buffer or must_write) and (total_bytes_written <= max_file_size)) {
410,143✔
734

735
        // buffer_mutex is used in this one place to prevent forced calls of write_data
736
        // to not overwrite data being written by the asynchronous thread.
737
        pthread_mutex_lock(&buffer_mutex) ;
45,713✔
738

739
        local_buffer_num = buffer_num ;
45,713✔
740
        if ( (local_buffer_num - writer_num) > max_num ) {
45,713✔
UNCOV
741
            num_to_write = max_num ;
×
742
        } else {
743
            num_to_write = (local_buffer_num - writer_num) ;
45,713✔
744
        }
745
        writer_num = local_buffer_num - num_to_write ;
45,713✔
746

747
        //! This loop pulls a "row" of time homogeneous data and writes it to the file
748
        while ( writer_num != local_buffer_num ) {
484,324✔
749

750
            writer_offset = writer_num % max_num ;
438,611✔
751
            //! keep record of bytes written to file. Default max is 1GB
752
            total_bytes_written += format_specific_write_data(writer_offset) ;
438,611✔
753
            writer_num++ ;
438,611✔
754

755
        }
756

757
        if(!max_size_warning && (total_bytes_written > max_file_size)) {
45,713✔
UNCOV
758
            std::cerr << "WARNING: Data record max file size " << (static_cast<double>(max_file_size))/(1<<20) << "MB reached.\n"
×
UNCOV
759
            "https://nasa.github.io/trick/documentation/simulation_capabilities/Data-Record#changing-the-max-file-size-of-a-data-record-group-ascii-and-binary-only" 
×
UNCOV
760
            << std::endl;
×
UNCOV
761
            max_size_warning = true;
×
762
        }
763

764
        pthread_mutex_unlock(&buffer_mutex) ;
45,713✔
765

766
    }
767

768
    return 0 ;
410,129✔
769
}
770

771
int Trick::DataRecordGroup::enable() {
34✔
772
    record = true ;
34✔
773
    return(0) ;
34✔
774
}
775

UNCOV
776
int Trick::DataRecordGroup::disable() {
×
UNCOV
777
    record = false ;
×
UNCOV
778
    return(0) ;
×
779
}
780

781
int Trick::DataRecordGroup::shutdown() {
49✔
782

783
    // Force write out all data
784
    record = true ; // If user disabled group, make sure any recorded data gets written out
49✔
785
    write_data(true) ;
49✔
786
    format_specific_shutdown() ;
49✔
787

788
    remove_all_variables();
49✔
789

790
    // remove_all_variables does not remove sim time
791
    if(!rec_buffer.empty()){
49✔
792
        delete rec_buffer[0];
49✔
793
        rec_buffer.clear();
49✔
794
    }
795

796
    if ( writer_buff ) {
49✔
797
        free(writer_buff) ;
49✔
798
        writer_buff = NULL ;
49✔
799
    }
800

801
    return 0 ;
49✔
802
}
803

804
std::string Trick::DataRecordGroup::type_string( int item_type, int item_size ) {
624✔
805
    switch (item_type) {
624✔
806
        case TRICK_CHARACTER:
2✔
807
                return "char";
2✔
808
                break;
809
        case TRICK_UNSIGNED_CHARACTER:
2✔
810
                return "unsigned_char";
2✔
811
                break;
UNCOV
812
        case TRICK_STRING:
×
UNCOV
813
                return "string";
×
814
                break;
815
        case TRICK_SHORT:
5✔
816
                return "short";
5✔
817
                break;
818
        case TRICK_UNSIGNED_SHORT:
5✔
819
                return "unsigned_short";
5✔
820
                break;
821
        case TRICK_ENUMERATED:
69✔
822
        case TRICK_INTEGER:
823
                return "int";
69✔
824
                break;
825
        case TRICK_UNSIGNED_INTEGER:
5✔
826
                return "unsigned_int";
5✔
827
                break;
828
        case TRICK_LONG:
5✔
829
                return "long";
5✔
830
                break;
831
        case TRICK_UNSIGNED_LONG:
5✔
832
                return "unsigned_long";
5✔
833
                break;
834
        case TRICK_FLOAT:
5✔
835
                return "float";
5✔
836
                break;
837
        case TRICK_DOUBLE:
454✔
838
                if ( single_prec_only ) {
454✔
UNCOV
839
                        return "float";
×
840
                }
841
                else {
842
                        return "double";
454✔
843
                }
844
                break;
845
        case TRICK_BITFIELD:
28✔
846
                if (item_size == sizeof(int)) {
28✔
847
                        return "int";
28✔
UNCOV
848
                } else if (item_size == sizeof(short)) {
×
849
                        return "short";
×
850
                } else {
UNCOV
851
                        return "char";
×
852
                }
853
                break;
854
        case TRICK_UNSIGNED_BITFIELD:
28✔
855
                if (item_size == sizeof(int)) {
28✔
856
                        return "unsigned_int";
28✔
UNCOV
857
                } else if (item_size == sizeof(short)) {
×
UNCOV
858
                        return "unsigned_short";
×
859
                } else {
UNCOV
860
                        return "unsigned_char";
×
861
                }
862
                break;
863
        case TRICK_LONG_LONG:
5✔
864
                return "long_long";
5✔
865
                break;
866
        case TRICK_UNSIGNED_LONG_LONG:
2✔
867
                return "unsigned_long_long";
2✔
868
                break;
869
        case TRICK_BOOLEAN:
4✔
870
#if ( __sun | __APPLE__ )
871
                return "int";
872
#else
873
                return "unsigned_char";
4✔
874
#endif
875
                break;
876
    }
UNCOV
877
    return "";
×
878
}
879

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