9c24f339cd3a91da3f683002fde48baa0db11c12
[lttv.git] / lttv / lttv / traceset.c
1 /* This file is part of the Linux Trace Toolkit viewer
2 * Copyright (C) 2003-2004 Michel Dagenais
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License Version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16 * MA 02111-1307, USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <lttv/traceset.h>
24 #include <lttv/iattribute.h>
25 #include <lttv/state.h>
26 #include <lttv/event.h>
27 #include <lttv/hook.h>
28 #include <stdio.h>
29 #include <babeltrace/babeltrace.h>
30 #include <babeltrace/context.h>
31 #include <babeltrace/ctf/iterator.h>
32 #include <babeltrace/ctf/events.h>
33
34 /* To traverse a tree recursively */
35 #include <fcntl.h>
36 /* For the use of realpath*/
37 #include <limits.h>
38 #include <stdlib.h>
39 /* For strcpy*/
40 #include <string.h>
41 #include <errno.h>
42 #include <dirent.h>
43 /* A trace is a sequence of events gathered in the same tracing session. The
44 events may be stored in several tracefiles in the same directory.
45 A trace set is defined when several traces are to be analyzed together,
46 possibly to study the interactions between events in the different traces.
47 */
48
49
50 LttvTraceset *lttv_traceset_new(void)
51 {
52 LttvTraceset *ts;
53 struct bt_iter_pos begin_pos;
54
55 ts = g_new(LttvTraceset, 1);
56 ts->filename = NULL;
57 ts->traces = g_ptr_array_new();
58 ts->context = bt_context_create();
59 ts->a = g_object_new(LTTV_ATTRIBUTE_TYPE, NULL);
60
61 /*Initialize iterator to the beginning of the traces*/
62 begin_pos.type = BT_SEEK_BEGIN;
63 ts->iter = bt_ctf_iter_create(ts->context, &begin_pos, NULL);
64
65 ts->event_hooks = lttv_hooks_new();
66
67 ts->state_trace_handle_index = g_ptr_array_new();
68 ts->has_precomputed_states = FALSE;
69
70 ts->time_span.start_time = ltt_time_zero;
71 ts->time_span.end_time = ltt_time_zero;
72 lttv_traceset_get_time_span_real(ts);
73 return ts;
74 }
75
76 char * lttv_traceset_name(LttvTraceset * s)
77 {
78 return s->filename;
79 }
80
81 #ifdef BABEL_CLEANUP
82 LttvTrace *lttv_trace_new(LttTrace *t)
83 {
84 LttvTrace *new_trace;
85
86 new_trace = g_new(LttvTrace, 1);
87 new_trace->a = g_object_new(LTTV_ATTRIBUTE_TYPE, NULL);
88 new_trace->id = t;
89 new_trace->ref_count = 0;
90 return new_trace;
91 }
92 #endif
93 /*
94 * get_absolute_pathname : Return the unique pathname in the system
95 *
96 * pathname is the relative path.
97 *
98 * abs_pathname is being set to the absolute path.
99 *
100 */
101 void get_absolute_pathname(const gchar *pathname, gchar * abs_pathname)
102 {
103 abs_pathname[0] = '\0';
104
105 if (realpath(pathname, abs_pathname) != NULL)
106 return;
107 else
108 {
109 /* error, return the original path unmodified */
110 strcpy(abs_pathname, pathname);
111 return;
112 }
113 return;
114 }
115
116
117
118 /*
119 * lttv_trace_create : Create a trace from a path
120 *
121 * ts is the traceset in which will be contained the trace
122 *
123 * path is the path where to find a trace. It is not recursive.
124 *
125 * This function is static since a trace should always be contained in a
126 * traceset.
127 *
128 * return the created trace or NULL on failure
129 */
130 static LttvTrace *lttv_trace_create(LttvTraceset *ts, const char *path)
131 {
132 int id = bt_context_add_trace(lttv_traceset_get_context(ts),
133 path,
134 "ctf",
135 NULL,
136 NULL,
137 NULL);
138 if (id < 0) {
139 return NULL;
140 }
141 // Create the trace and save the trace handle id returned by babeltrace
142 LttvTrace *new_trace;
143
144 new_trace = g_new(LttvTrace, 1);
145 new_trace->a = g_object_new(LTTV_ATTRIBUTE_TYPE, NULL);
146 new_trace->id = id;
147 new_trace->ref_count = 0;
148 new_trace->traceset = ts;
149 new_trace->state = g_new(LttvTraceState,1);
150 lttv_trace_state_init(new_trace->state,new_trace);
151
152 /* Add the state to the trace_handle to state index */
153 g_ptr_array_set_size(ts->state_trace_handle_index,id+1);
154 g_ptr_array_index(ts->state_trace_handle_index,id) = new_trace->state;
155
156 return new_trace;
157 }
158
159 /*
160 * lttv_trace_create : Create and add a single trace to a traceset
161 *
162 * ts is the traceset in which will be contained the trace
163 *
164 * path is the path where to find a trace. It is not recursive.
165 *
166 * return a positive integer (>=0)on success or -1 on failure
167 */
168 static int lttv_traceset_create_trace(LttvTraceset *ts, const char *path)
169 {
170 LttvTrace *trace = lttv_trace_create(ts, path);
171 if (trace == NULL) {
172 return -1;
173 }
174 lttv_traceset_add(ts, trace);
175 return 0;
176 }
177
178 LttvTraceset *lttv_traceset_copy(LttvTraceset *s_orig)
179 {
180 guint i;
181 LttvTraceset *s;
182 LttvTrace * trace;
183
184 s = g_new(LttvTraceset, 1);
185 s->filename = NULL;
186 s->traces = g_ptr_array_new();
187 s->state_trace_handle_index = g_ptr_array_new();
188 for(i=0;i<s_orig->traces->len;i++)
189 {
190 trace = g_ptr_array_index(s_orig->traces, i);
191 trace->ref_count++;
192
193 /* WARNING: this is an alias, not a copy. */
194 g_ptr_array_add(s->traces, trace);
195
196 g_ptr_array_set_size(s->state_trace_handle_index,trace->id+1);
197 g_ptr_array_index(s->state_trace_handle_index,trace->id) = trace->state;
198
199 }
200 s->context = s_orig->context;
201 bt_context_get(s->context);
202 s->a = LTTV_ATTRIBUTE(lttv_iattribute_deep_copy(LTTV_IATTRIBUTE(s_orig->a)));
203 return s;
204 }
205
206
207 LttvTraceset *lttv_traceset_load(const gchar *filename)
208 {
209 LttvTraceset *s = g_new(LttvTraceset,1);
210 FILE *tf;
211
212 s->filename = g_strdup(filename);
213 tf = fopen(filename,"r");
214
215 g_critical("NOT IMPLEMENTED : load traceset data from a XML file");
216
217 fclose(tf);
218 return s;
219 }
220
221 gint lttv_traceset_save(LttvTraceset *s)
222 {
223 FILE *tf;
224
225 tf = fopen(s->filename, "w");
226
227 g_critical("NOT IMPLEMENTED : save traceset data in a XML file");
228
229 fclose(tf);
230 return 0;
231 }
232
233 void lttv_traceset_destroy(LttvTraceset *s)
234 {
235 guint i;
236
237 for(i=0;i<s->traces->len;i++) {
238 LttvTrace *trace = g_ptr_array_index(s->traces, i);
239 lttv_trace_unref(trace);
240 // todo mdenis 2012-03-27: uncomment when babeltrace gets fixed
241 //bt_context_remove_trace(lttv_traceset_get_context(s), trace->id);
242 if(lttv_trace_get_ref_number(trace) == 0)
243 lttv_trace_destroy(trace);
244 }
245 g_ptr_array_free(s->traces, TRUE);
246 bt_context_put(s->context);
247 g_object_unref(s->a);
248 g_free(s);
249 }
250
251 struct bt_context *lttv_traceset_get_context(LttvTraceset *s)
252 {
253 return s->context;
254 }
255
256 LttvTraceset *lttv_trace_get_traceset(LttvTrace *trace)
257 {
258 return trace->traceset;
259 }
260
261 LttvHooks *lttv_traceset_get_hooks(LttvTraceset *s)
262 {
263 return s->event_hooks;
264 }
265
266 void lttv_trace_destroy(LttvTrace *t)
267 {
268 g_object_unref(t->a);
269 g_free(t);
270 }
271
272 void lttv_traceset_add(LttvTraceset *s, LttvTrace *t)
273 {
274 t->ref_count++;
275 g_ptr_array_add(s->traces, t);
276 }
277
278 int lttv_traceset_add_path(LttvTraceset *ts, char *trace_path)
279 {
280 int ret = -1;
281 DIR *curdir = NULL;
282 gboolean metaFileFound = FALSE;
283
284 /* Open top level directory */
285 curdir = opendir(trace_path);
286 if (curdir == NULL) {
287 g_warning("Cannot open directory %s (%s)", trace_path, strerror(errno));
288 return ret;
289 }
290
291 // Check if a metadata file exists in the current directory
292 int metafd = openat(dirfd(curdir), "metadata", O_RDONLY);
293 if (metafd < 0) {
294
295 } else {
296 ret = close(metafd);
297 if (ret < 0) {
298 g_warning("Unable to close metadata "
299 "file descriptor : %s.", trace_path);
300 goto error;
301 }
302
303 ret = lttv_traceset_create_trace(ts, trace_path);
304 if (ret < 0) {
305 g_warning("Opening trace \"%s\" "
306 "for reading.", trace_path);
307 goto error;
308 }
309 metaFileFound = TRUE;
310 }
311
312 struct dirent curentry;
313 struct dirent *resultentry;
314 while ((ret = readdir_r(curdir, &curentry, &resultentry)) == 0) {
315 if (resultentry == NULL) {
316 /* No more entry*/
317 break;
318 }
319 if (curentry.d_name[0] != '.') {
320 if (curentry.d_type == DT_DIR) {
321
322 char curpath[PATH_MAX];
323 snprintf(curpath, PATH_MAX, "%s/%s", trace_path, curentry.d_name);
324 ret = lttv_traceset_add_path(ts, curpath);
325 if (ret >= 0) {
326 metaFileFound = TRUE;
327 }
328 }
329 }
330 }
331
332 if (ret != 0) {
333 g_warning("Invalid readdir");
334 }
335
336 error:
337 if(metaFileFound)
338 return ret;
339 else
340 return -1;
341 }
342
343 unsigned lttv_traceset_number(LttvTraceset *s)
344 {
345 return s->traces->len;
346 }
347
348
349 LttvTrace *lttv_traceset_get(LttvTraceset *s, unsigned i)
350 {
351 g_assert(s->traces->len > i);
352 return ((LttvTrace *)s->traces->pdata[i]);
353 }
354
355
356 void lttv_traceset_remove(LttvTraceset *s, unsigned i)
357 {
358 LttvTrace * t;
359 g_assert(s->traces->len > i);
360 t = (LttvTrace *)s->traces->pdata[i];
361 t->ref_count--;
362 bt_context_remove_trace(lttv_traceset_get_context(s), t->id);
363 g_ptr_array_remove_index(s->traces, i);
364 }
365
366
367 /* A set of attributes is attached to each trace set, trace and tracefile
368 to store user defined data as needed. */
369
370 LttvAttribute *lttv_traceset_attribute(LttvTraceset *s)
371 {
372 return s->a;
373 }
374
375
376 LttvAttribute *lttv_trace_attribute(LttvTrace *t)
377 {
378 return t->a;
379 }
380
381
382 gint lttv_trace_get_id(LttvTrace *t)
383 {
384 return t->id;
385 }
386
387 guint lttv_trace_get_ref_number(LttvTrace * t)
388 {
389 // todo mdenis: adapt to babeltrace
390 return t->ref_count;
391 }
392
393 guint lttv_trace_ref(LttvTrace * t)
394 {
395 t->ref_count++;
396
397 return t->ref_count;
398 }
399
400 guint lttv_trace_unref(LttvTrace * t)
401 {
402 if(likely(t->ref_count > 0))
403 t->ref_count--;
404
405 return t->ref_count;
406 }
407
408 guint lttv_trace_get_num_cpu(LttvTrace *t)
409 {
410 #warning "TODO - Set the right number of CPU"
411 return 24;
412 }
413
414 LttvTracesetPosition *lttv_traceset_create_current_position(LttvTraceset *traceset)
415 {
416 LttvTracesetPosition *traceset_pos;
417
418 traceset_pos = g_new(LttvTracesetPosition, 1);
419
420 /* Check if the new passed */
421 if(traceset_pos == NULL) {
422 return NULL;
423 }
424
425 traceset_pos->iter = traceset->iter;
426 traceset_pos->bt_pos = bt_iter_get_pos(bt_ctf_get_iter(traceset->iter));
427 traceset_pos->timestamp = G_MAXUINT64;
428 traceset_pos->cpu_id = INT_MAX;
429
430 return traceset_pos;
431 }
432
433 LttvTracesetPosition *lttv_traceset_create_time_position(LttvTraceset *traceset,
434 LttTime timestamp)
435 {
436 LttvTracesetPosition *traceset_pos;
437
438 traceset_pos = g_new(LttvTracesetPosition, 1);
439
440 /* Check if the new passed */
441 if(traceset_pos == NULL) {
442 return NULL;
443 }
444
445 traceset_pos->iter = traceset->iter;
446 traceset_pos->bt_pos = bt_iter_create_time_pos(
447 bt_ctf_get_iter(traceset_pos->iter),
448 ltt_time_to_uint64(timestamp));
449 traceset_pos->timestamp = G_MAXUINT64;
450 traceset_pos->cpu_id = INT_MAX;
451 return traceset_pos;
452 }
453
454 void lttv_traceset_destroy_position(LttvTracesetPosition *traceset_pos)
455 {
456 bt_iter_free_pos(traceset_pos->bt_pos);
457 g_free(traceset_pos);
458 }
459
460 void lttv_traceset_seek_to_position(const LttvTracesetPosition *traceset_pos)
461 {
462 bt_iter_set_pos(bt_ctf_get_iter(traceset_pos->iter), traceset_pos->bt_pos);
463 }
464
465 guint lttv_traceset_get_cpuid_from_event(LttvEvent *event)
466 {
467 unsigned long timestamp;
468 unsigned int cpu_id;
469
470 struct bt_ctf_event *ctf_event = event->bt_event;
471 timestamp = bt_ctf_get_timestamp(ctf_event);
472 if (timestamp == -1ULL) {
473 return 0;
474 }
475 const struct bt_definition *scope = bt_ctf_get_top_level_scope(ctf_event, BT_STREAM_PACKET_CONTEXT);
476 if (bt_ctf_field_get_error()) {
477 return 0;
478 }
479 cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(ctf_event, scope, "cpu_id"));
480 if (bt_ctf_field_get_error()) {
481 return 0;
482 } else {
483 return cpu_id;
484 }
485 }
486
487 guint64 lttv_traceset_get_timestamp_first_event(LttvTraceset *ts)
488 {
489 LttvTracesetPosition begin_position;
490 struct bt_iter_pos pos;
491 begin_position.bt_pos = &pos;
492 begin_position.timestamp = G_MAXUINT64;
493 begin_position.cpu_id = INT_MAX;
494
495 /* Assign iterator to the beginning of the traces */
496 begin_position.bt_pos->type = BT_SEEK_BEGIN;
497 begin_position.iter = ts->iter;
498
499 return lttv_traceset_position_get_timestamp(&begin_position);
500 }
501
502 guint64 lttv_traceset_get_timestamp_last_event(LttvTraceset *ts)
503 {
504 LttvTracesetPosition last_position;
505 struct bt_iter_pos pos;
506 last_position.bt_pos = &pos;
507 last_position.timestamp = G_MAXUINT64;
508 last_position.cpu_id = INT_MAX;
509
510 /* Assign iterator to the last event of the traces */
511 last_position.bt_pos->type = BT_SEEK_LAST;
512 last_position.iter = ts->iter;
513
514 return lttv_traceset_position_get_timestamp(&last_position);
515 }
516
517 /*
518 * lttv_traceset_get_timestamp_begin : returns the minimum timestamp of
519 * all the traces in the traceset.
520 *
521 */
522 guint64 lttv_traceset_get_timestamp_begin(LttvTraceset *traceset)
523 {
524 struct bt_context *bt_ctx;
525 bt_ctx = lttv_traceset_get_context(traceset);
526 guint64 timestamp_min, timestamp_cur = 0;
527 int i;
528 int trace_count;
529 LttvTrace *currentTrace;
530 trace_count = traceset->traces->len;
531 if(trace_count == 0)
532 timestamp_min = 0;
533 else{
534 timestamp_min = G_MAXUINT64;
535 for(i = 0; i < trace_count;i++)
536 {
537 currentTrace = g_ptr_array_index(traceset->traces,i);
538 timestamp_cur = bt_trace_handle_get_timestamp_begin(bt_ctx,
539 currentTrace->id,
540 BT_CLOCK_REAL);
541 if(timestamp_cur < timestamp_min)
542 timestamp_min = timestamp_cur;
543 }
544 }
545 return timestamp_min;
546 }
547
548 /*
549 * lttv_traceset_get_timestamp_end: returns the maximum timestamp of
550 * all the traces in the traceset.
551 *
552 */
553 guint64 lttv_traceset_get_timestamp_end(LttvTraceset *traceset)
554 {
555 struct bt_context *bt_ctx;
556 bt_ctx = lttv_traceset_get_context(traceset);
557 guint64 timestamp_max, timestamp_cur = 0;
558 int i;
559 int trace_count;
560 LttvTrace *currentTrace;
561 trace_count = traceset->traces->len;
562
563 if(trace_count == 0){
564 timestamp_max = 1;
565 }
566 else{
567 timestamp_max = 0;
568 for(i =0; i < trace_count;i++)
569 {
570 currentTrace = g_ptr_array_index(traceset->traces,i);
571 timestamp_cur = bt_trace_handle_get_timestamp_end(bt_ctx,
572 currentTrace->id,
573 BT_CLOCK_REAL);
574 if(timestamp_cur > timestamp_max){
575 timestamp_max = timestamp_cur;
576 }
577 }
578 }
579 return timestamp_max;
580 }
581 /*
582 * lttv_traceset_get_time_span_real : return a TimeInterval representing the
583 * minimum timestamp and the maximum timestamp of the traceset.
584 *
585 */
586 TimeInterval lttv_traceset_get_time_span_real(LttvTraceset *ts)
587 {
588
589
590 if(ltt_time_compare(ts->time_span.start_time,
591 ltt_time_zero) == 0 && ts->traces->len > 0){
592 ts->time_span.start_time = ltt_time_from_uint64(
593 lttv_traceset_get_timestamp_first_event(ts));
594 ts->time_span.end_time = ltt_time_from_uint64(
595 lttv_traceset_get_timestamp_last_event(ts));
596 }
597 return ts->time_span;
598 }
599
600 /*
601 * lttv_traceset_get_time_span : return a TimeInterval representing the
602 * minimum timestamp and the maximum timestamp of the traceset.
603 *
604 */
605 TimeInterval lttv_traceset_get_time_span(LttvTraceset *ts)
606 {
607 if(ltt_time_compare(ts->time_span.start_time, ltt_time_zero) == 0){
608 ts->time_span.start_time =ltt_time_from_uint64(
609 lttv_traceset_get_timestamp_begin(ts));
610 ts->time_span.end_time = ltt_time_from_uint64(
611 lttv_traceset_get_timestamp_end(ts));
612 }
613 return ts->time_span;
614 }
615
616 const char *lttv_traceset_get_name_from_event(LttvEvent *event)
617 {
618 return bt_ctf_event_name(event->bt_event);
619 }
620
621 int set_values_position(const LttvTracesetPosition *pos)
622 {
623 LttvTracesetPosition previous_pos;
624 previous_pos.iter = pos->iter;
625 previous_pos.bt_pos = bt_iter_get_pos(bt_ctf_get_iter(pos->iter));
626 /* Seek to the new desired position */
627 lttv_traceset_seek_to_position(pos);
628 /*Read the event*/
629 struct bt_ctf_event *event = bt_ctf_iter_read_event(pos->iter);
630
631 if(event != NULL){
632 ((LttvTracesetPosition *)pos)->timestamp = bt_ctf_get_timestamp(event);
633
634 LttvEvent lttv_event;
635 lttv_event.bt_event = event;
636 ((LttvTracesetPosition *)pos)->cpu_id = lttv_traceset_get_cpuid_from_event(&lttv_event);
637 }
638 else {
639 /* The event is null */
640 return 0;
641 }
642
643 /* Reassign the previously saved position */
644 lttv_traceset_seek_to_position(&previous_pos);
645 /*We must desallocate because the function bt_iter_get_pos() does a g_new */
646 bt_iter_free_pos(previous_pos.bt_pos);
647 if (pos->timestamp == G_MAXUINT64) {
648 return 0;
649 }
650 return 1;
651 }
652
653 guint64 lttv_traceset_position_get_timestamp(const LttvTracesetPosition *pos)
654 {
655 if(pos->timestamp == G_MAXUINT64){
656 if(set_values_position(pos) == 0){
657 return 0;
658 }
659 }
660
661 return pos->timestamp;
662 }
663
664 int lttv_traceset_position_get_cpuid(const LttvTracesetPosition *pos){
665 if(pos->cpu_id == INT_MAX ){
666 if(set_values_position(pos) == 0){
667 return 0;
668 }
669 }
670 return pos->cpu_id;
671 }
672
673 LttTime lttv_traceset_position_get_time(const LttvTracesetPosition *pos)
674 {
675 return ltt_time_from_uint64(lttv_traceset_position_get_timestamp(pos));
676 }
677
678
679 /* 0 if equals, other is different */
680 int lttv_traceset_position_compare(const LttvTracesetPosition *pos1, const LttvTracesetPosition *pos2)
681 {
682 #warning " TODO :Rename for lttv_traceset_position_equals && Must return COMPARAISON OF THE 2 POSITION && verify if it is the best way to compare position"
683 if(pos1 == NULL || pos2 == NULL){
684 return -1;
685 }
686
687 int res = -1;
688 #ifdef HAVE_BT_ITER_EQUALS_POS
689 if(pos1->timestamp == G_MAXUINT64 || pos2->timestamp == G_MAXUINT64) {
690 res = bt_iter_equals_pos(pos1->bt_pos, pos2->bt_pos);
691 }
692 #endif
693 if (res < 0) {
694
695 guint64 timeStampPos1,timeStampPos2;
696 guint cpuId1, cpuId2;
697
698 timeStampPos1 = lttv_traceset_position_get_timestamp(pos1);
699 timeStampPos2 = lttv_traceset_position_get_timestamp(pos2);
700
701 if (timeStampPos1 == timeStampPos2) {
702
703 cpuId1 = lttv_traceset_position_get_cpuid(pos1);
704 cpuId2 = lttv_traceset_position_get_cpuid(pos2);
705
706 if(cpuId1 == cpuId2){
707 return 0;
708 }
709 }
710 return 1;
711 } else {
712
713 return !res;
714 }
715 }
716
717 int lttv_traceset_position_time_compare(const LttvTracesetPosition *pos1,
718 const LttvTracesetPosition *pos2)
719 {
720 guint64 timeStampPos1,timeStampPos2;
721
722 timeStampPos1 = lttv_traceset_position_get_timestamp(pos1);
723 timeStampPos2 = lttv_traceset_position_get_timestamp(pos2);
724
725 return timeStampPos1 - timeStampPos2;
726 }
727 int lttv_traceset_position_compare_current(const LttvTraceset *ts,
728 const LttvTracesetPosition *pos)
729 {
730 int result = 0;
731 LttvTracesetPosition *curPos = lttv_traceset_create_current_position(ts);
732
733 result = lttv_traceset_position_compare(curPos,pos);
734
735 lttv_traceset_destroy_position(curPos);
736
737 return result;
738 }
739
740 LttTime lttv_traceset_get_current_time(const LttvTraceset *ts)
741 {
742 LttvTracesetPosition *curPos = lttv_traceset_create_current_position(ts);
743 guint64 currentTimestamp = lttv_traceset_position_get_timestamp(curPos);
744 lttv_traceset_destroy_position(curPos);
745
746 return ltt_time_from_uint64(currentTimestamp);
747 }
This page took 0.041767 seconds and 3 git commands to generate.