multiple traces/tracefiles change
[lttv.git] / ltt / branches / poly / lttv / main / state.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
20 #include <lttv/state.h>
21 #include <ltt/facility.h>
22 #include <ltt/trace.h>
23 #include <ltt/event.h>
24 #include <ltt/type.h>
25
26 LttvExecutionMode
27 LTTV_STATE_MODE_UNKNOWN,
28 LTTV_STATE_USER_MODE,
29 LTTV_STATE_SYSCALL,
30 LTTV_STATE_TRAP,
31 LTTV_STATE_IRQ;
32
33 LttvExecutionSubmode
34 LTTV_STATE_SUBMODE_UNKNOWN,
35 LTTV_STATE_SUBMODE_NONE;
36
37 LttvProcessStatus
38 LTTV_STATE_UNNAMED,
39 LTTV_STATE_WAIT_FORK,
40 LTTV_STATE_WAIT_CPU,
41 LTTV_STATE_EXIT,
42 LTTV_STATE_WAIT,
43 LTTV_STATE_RUN;
44
45 static GQuark
46 LTTV_STATE_TRACEFILES,
47 LTTV_STATE_PROCESSES,
48 LTTV_STATE_PROCESS,
49 LTTV_STATE_EVENT,
50 LTTV_STATE_SAVED_STATES,
51 LTTV_STATE_TIME,
52 LTTV_STATE_HOOKS;
53
54
55 static void fill_name_tables(LttvTraceState *tcs);
56
57 static void free_name_tables(LttvTraceState *tcs);
58
59 static void lttv_state_free_process_table(GHashTable *processes);
60
61 static LttvProcessState *create_process(LttvTracefileState *tfs,
62 LttvProcessState *parent, guint pid);
63
64 static LttvProcessState *create_process_from_trace(LttvTraceState *ts,
65 LttvProcessState *parent, guint pid);
66
67 void lttv_state_save(LttvTraceState *self, LttvAttribute *container)
68 {
69 LTTV_TRACE_STATE_GET_CLASS(self)->state_save(self, container);
70 }
71
72
73 void lttv_state_restore(LttvTraceState *self, LttvAttribute *container)
74 {
75 LTTV_TRACE_STATE_GET_CLASS(self)->state_restore(self, container);
76 }
77
78
79 void lttv_state_saved_state_free(LttvTraceState *self,
80 LttvAttribute *container)
81 {
82 LTTV_TRACE_STATE_GET_CLASS(self)->state_restore(self, container);
83 }
84
85
86 static void
87 restore_init_state(LttvTraceState *self)
88 {
89 guint i, nb_control, nb_per_cpu, nb_tracefile;
90
91 LttvTracefileState *tfcs;
92
93 LttTime null_time = {0,0};
94
95 if(self->processes != NULL) lttv_state_free_process_table(self->processes);
96 self->processes = g_hash_table_new(g_direct_hash, g_direct_equal);
97 self->nb_event = 0;
98
99 nb_control = ltt_trace_control_tracefile_number(self->parent.t);
100 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(self->parent.t);
101 nb_tracefile = nb_control + nb_per_cpu;
102 for(i = 0 ; i < nb_tracefile ; i++) {
103 if(i < nb_control) {
104 tfcs = LTTV_TRACEFILE_STATE(self->parent.control_tracefiles[i]);
105 }
106 else {
107 tfcs = LTTV_TRACEFILE_STATE(self->parent.per_cpu_tracefiles[i - nb_control]);
108 }
109
110 tfcs->parent.timestamp = null_time;
111 tfcs->saved_position = 0;
112 tfcs->process = create_process(tfcs, NULL,0);
113 }
114 }
115
116
117 static void
118 init(LttvTracesetState *self, LttvTraceset *ts)
119 {
120 guint i, j, nb_trace, nb_control, nb_per_cpu, nb_tracefile;
121
122 LttvTraceContext *tc;
123
124 LttvTraceState *tcs;
125
126 LttvTracefileState *tfcs;
127
128 LTTV_TRACESET_CONTEXT_CLASS(g_type_class_peek(LTTV_TRACESET_CONTEXT_TYPE))->
129 init((LttvTracesetContext *)self, ts);
130
131 nb_trace = lttv_traceset_number(ts);
132 for(i = 0 ; i < nb_trace ; i++) {
133 tc = self->parent.traces[i];
134 tcs = (LttvTraceState *)tc;
135 tcs->save_interval = 100000;
136 tcs->recompute_state_in_seek = TRUE;
137 tcs->saved_state_ready = FALSE;
138 fill_name_tables(tcs);
139
140 nb_control = ltt_trace_control_tracefile_number(tc->t);
141 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(tc->t);
142 nb_tracefile = nb_control + nb_per_cpu;
143 for(j = 0 ; j < nb_tracefile ; j++) {
144 if(j < nb_control) {
145 tfcs = LTTV_TRACEFILE_STATE(tc->control_tracefiles[j]);
146 }
147 else {
148 tfcs = LTTV_TRACEFILE_STATE(tc->per_cpu_tracefiles[j - nb_control]);
149 }
150 tfcs->cpu_name= g_quark_from_string(ltt_tracefile_name(tfcs->parent.tf));
151 }
152 tcs->processes = NULL;
153 restore_init_state(tcs);
154 }
155 }
156
157
158 static void
159 fini(LttvTracesetState *self)
160 {
161 guint i, j, nb_trace, nb_tracefile;
162
163 LttvTraceState *tcs;
164
165 LttvTracefileState *tfcs;
166
167 nb_trace = lttv_traceset_number(LTTV_TRACESET_CONTEXT(self)->ts);
168 for(i = 0 ; i < nb_trace ; i++) {
169 tcs = (LttvTraceState *)(LTTV_TRACESET_CONTEXT(self)->traces[i]);
170 lttv_state_free_process_table(tcs->processes);
171 tcs->processes = NULL;
172 free_name_tables(tcs);
173 }
174 LTTV_TRACESET_CONTEXT_CLASS(g_type_class_peek(LTTV_TRACESET_CONTEXT_TYPE))->
175 fini((LttvTracesetContext *)self);
176 }
177
178
179 static LttvTracesetContext *
180 new_traceset_context(LttvTracesetContext *self)
181 {
182 return LTTV_TRACESET_CONTEXT(g_object_new(LTTV_TRACESET_STATE_TYPE, NULL));
183 }
184
185
186 static LttvTraceContext *
187 new_trace_context(LttvTracesetContext *self)
188 {
189 return LTTV_TRACE_CONTEXT(g_object_new(LTTV_TRACE_STATE_TYPE, NULL));
190 }
191
192
193 static LttvTracefileContext *
194 new_tracefile_context(LttvTracesetContext *self)
195 {
196 return LTTV_TRACEFILE_CONTEXT(g_object_new(LTTV_TRACEFILE_STATE_TYPE, NULL));
197 }
198
199
200 static void copy_process_state(gpointer key, gpointer value,gpointer user_data)
201 {
202 LttvProcessState *process, *new_process;
203
204 GHashTable *new_processes = (GHashTable *)user_data;
205
206 guint i;
207
208 process = (LttvProcessState *)value;
209 new_process = g_new(LttvProcessState, 1);
210 *new_process = *process;
211 new_process->execution_stack = g_array_new(FALSE, FALSE,
212 sizeof(LttvExecutionState));
213 g_array_set_size(new_process->execution_stack,process->execution_stack->len);
214 for(i = 0 ; i < process->execution_stack->len; i++) {
215 g_array_index(new_process->execution_stack, LttvExecutionState, i) =
216 g_array_index(process->execution_stack, LttvExecutionState, i);
217 }
218 new_process->state = &g_array_index(new_process->execution_stack,
219 LttvExecutionState, new_process->execution_stack->len - 1);
220 g_hash_table_insert(new_processes, GUINT_TO_POINTER(new_process->pid),
221 new_process);
222 }
223
224
225 static GHashTable *lttv_state_copy_process_table(GHashTable *processes)
226 {
227 GHashTable *new_processes = g_hash_table_new(g_direct_hash, g_direct_equal);
228
229 g_hash_table_foreach(processes, copy_process_state, new_processes);
230 return new_processes;
231 }
232
233
234 /* The saved state for each trace contains a member "processes", which
235 stores a copy of the process table, and a member "tracefiles" with
236 one entry per tracefile. Each tracefile has a "process" member pointing
237 to the current process and a "position" member storing the tracefile
238 position (needed to seek to the current "next" event. */
239
240 static void state_save(LttvTraceState *self, LttvAttribute *container)
241 {
242 guint i, nb_control, nb_per_cpu, nb_tracefile;
243
244 LttvTracefileState *tfcs;
245
246 LttvAttribute *tracefiles_tree, *tracefile_tree;
247
248 LttvAttributeType type;
249
250 LttvAttributeValue value;
251
252 LttvAttributeName name;
253
254 LttEventPosition *ep;
255
256 tracefiles_tree = lttv_attribute_find_subdir(container,
257 LTTV_STATE_TRACEFILES);
258
259 value = lttv_attribute_add(container, LTTV_STATE_PROCESSES,
260 LTTV_POINTER);
261 *(value.v_pointer) = lttv_state_copy_process_table(self->processes);
262
263 nb_control = ltt_trace_control_tracefile_number(self->parent.t);
264 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(self->parent.t);
265 nb_tracefile = nb_control + nb_per_cpu;
266
267 for(i = 0 ; i < nb_tracefile ; i++) {
268 if(i < nb_control)
269 tfcs = (LttvTracefileState *)self->parent.control_tracefiles[i];
270 else tfcs = (LttvTracefileState *)
271 self->parent.per_cpu_tracefiles[i - nb_control];
272
273 tracefile_tree = g_object_new(LTTV_ATTRIBUTE_TYPE, NULL);
274 value = lttv_attribute_add(tracefiles_tree, i,
275 LTTV_GOBJECT);
276 *(value.v_gobject) = (GObject *)tracefile_tree;
277 value = lttv_attribute_add(tracefile_tree, LTTV_STATE_PROCESS,
278 LTTV_UINT);
279 *(value.v_uint) = tfcs->process->pid;
280 value = lttv_attribute_add(tracefile_tree, LTTV_STATE_EVENT,
281 LTTV_POINTER);
282 if(tfcs->parent.e == NULL) *(value.v_pointer) = NULL;
283 else {
284 ep = ltt_event_position_new();
285 ltt_event_position(tfcs->parent.e, ep);
286 *(value.v_pointer) = ep;
287 }
288 }
289 }
290
291
292 static void state_restore(LttvTraceState *self, LttvAttribute *container)
293 {
294 guint i, nb_control, nb_per_cpu, nb_tracefile;
295
296 LttvTracefileState *tfcs;
297
298 LttvAttribute *tracefiles_tree, *tracefile_tree;
299
300 LttvAttributeType type;
301
302 LttvAttributeValue value;
303
304 LttvAttributeName name;
305
306 LttEventPosition *ep;
307
308 tracefiles_tree = lttv_attribute_find_subdir(container,
309 LTTV_STATE_TRACEFILES);
310
311 type = lttv_attribute_get_by_name(container, LTTV_STATE_PROCESSES,
312 &value);
313 g_assert(type == LTTV_POINTER);
314 lttv_state_free_process_table(self->processes);
315 self->processes = lttv_state_copy_process_table(*(value.v_pointer));
316
317 nb_control = ltt_trace_control_tracefile_number(self->parent.t);
318 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(self->parent.t);
319 nb_tracefile = nb_control + nb_per_cpu;
320
321 for(i = 0 ; i < nb_tracefile ; i++) {
322 if(i < nb_control) tfcs = (LttvTracefileState *)
323 self->parent.control_tracefiles[i];
324 else tfcs = (LttvTracefileState *)
325 self->parent.per_cpu_tracefiles[i - nb_control];
326
327 type = lttv_attribute_get(tracefiles_tree, i, &name, &value);
328 g_assert(type == LTTV_GOBJECT);
329 tracefile_tree = *((LttvAttribute **)(value.v_gobject));
330
331 type = lttv_attribute_get_by_name(tracefile_tree, LTTV_STATE_PROCESS,
332 &value);
333 g_assert(type == LTTV_UINT);
334 tfcs->process = lttv_state_find_process(tfcs, *(value.v_uint));
335 type = lttv_attribute_get_by_name(tracefile_tree, LTTV_STATE_EVENT,
336 &value);
337 g_assert(type == LTTV_POINTER);
338 if(*(value.v_pointer) == NULL) tfcs->parent.e = NULL;
339 else {
340 ep = *(value.v_pointer);
341 ltt_tracefile_seek_position(tfcs->parent.tf, ep);
342 tfcs->parent.e = ltt_tracefile_read(tfcs->parent.tf);
343 tfcs->parent.timestamp = ltt_event_time(tfcs->parent.e);
344 }
345 }
346 }
347
348
349 static void state_saved_free(LttvTraceState *self, LttvAttribute *container)
350 {
351 guint i, nb_control, nb_per_cpu, nb_tracefile;
352
353 LttvTracefileState *tfcs;
354
355 LttvAttribute *tracefiles_tree, *tracefile_tree;
356
357 LttvAttributeType type;
358
359 LttvAttributeValue value;
360
361 LttvAttributeName name;
362
363 LttEventPosition *ep;
364
365 tracefiles_tree = lttv_attribute_find_subdir(container,
366 LTTV_STATE_TRACEFILES);
367 lttv_attribute_remove_by_name(container, LTTV_STATE_TRACEFILES);
368
369 type = lttv_attribute_get_by_name(container, LTTV_STATE_PROCESSES,
370 &value);
371 g_assert(type == LTTV_POINTER);
372 lttv_state_free_process_table(*(value.v_pointer));
373 *(value.v_pointer) = NULL;
374 lttv_attribute_remove_by_name(container, LTTV_STATE_PROCESSES);
375
376 nb_control = ltt_trace_control_tracefile_number(self->parent.t);
377 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(self->parent.t);
378 nb_tracefile = nb_control + nb_per_cpu;
379
380 for(i = 0 ; i < nb_tracefile ; i++) {
381 if(i < nb_control) tfcs = (LttvTracefileState *)
382 self->parent.control_tracefiles[i];
383 else tfcs = (LttvTracefileState *)
384 self->parent.per_cpu_tracefiles[i - nb_control];
385
386 type = lttv_attribute_get(tracefiles_tree, i, &name, &value);
387 g_assert(type == LTTV_GOBJECT);
388 tracefile_tree = *((LttvAttribute **)(value.v_gobject));
389
390 type = lttv_attribute_get_by_name(tracefile_tree, LTTV_STATE_EVENT,
391 &value);
392 g_assert(type == LTTV_POINTER);
393 if(*(value.v_pointer) != NULL) g_free(*(value.v_pointer));
394 }
395 lttv_attribute_recursive_free(tracefiles_tree);
396 }
397
398
399 static void
400 fill_name_tables(LttvTraceState *tcs)
401 {
402 int i, nb;
403
404 char *f_name, *e_name;
405
406 LttvTraceHook h;
407
408 LttEventType *et;
409
410 LttType *t;
411
412 GString *fe_name = g_string_new("");
413
414 nb = ltt_trace_eventtype_number(tcs->parent.t);
415 tcs->eventtype_names = g_new(GQuark, nb);
416 for(i = 0 ; i < nb ; i++) {
417 et = ltt_trace_eventtype_get(tcs->parent.t, i);
418 e_name = ltt_eventtype_name(et);
419 f_name = ltt_facility_name(ltt_eventtype_facility(et));
420 g_string_printf(fe_name, "%s.%s", f_name, e_name);
421 tcs->eventtype_names[i] = g_quark_from_string(fe_name->str);
422 }
423
424 lttv_trace_find_hook(tcs->parent.t, "core", "syscall_entry",
425 "syscall_id", NULL, NULL, NULL, &h);
426 t = ltt_field_type(h.f1);
427 nb = ltt_type_element_number(t);
428
429 /* CHECK syscalls should be an emun but currently are not!
430 tcs->syscall_names = g_new(GQuark, nb);
431
432 for(i = 0 ; i < nb ; i++) {
433 tcs->syscall_names[i] = g_quark_from_string(ltt_enum_string_get(t, i));
434 }
435 */
436
437 tcs->syscall_names = g_new(GQuark, 256);
438 for(i = 0 ; i < 256 ; i++) {
439 g_string_printf(fe_name, "syscall %d", i);
440 tcs->syscall_names[i] = g_quark_from_string(fe_name->str);
441 }
442
443 lttv_trace_find_hook(tcs->parent.t, "core", "trap_entry",
444 "trap_id", NULL, NULL, NULL, &h);
445 t = ltt_field_type(h.f1);
446 nb = ltt_type_element_number(t);
447
448 /*
449 tcs->trap_names = g_new(GQuark, nb);
450 for(i = 0 ; i < nb ; i++) {
451 tcs->trap_names[i] = g_quark_from_string(ltt_enum_string_get(t, i));
452 }
453 */
454
455 tcs->trap_names = g_new(GQuark, 256);
456 for(i = 0 ; i < 256 ; i++) {
457 g_string_printf(fe_name, "trap %d", i);
458 tcs->trap_names[i] = g_quark_from_string(fe_name->str);
459 }
460
461 lttv_trace_find_hook(tcs->parent.t, "core", "irq_entry",
462 "irq_id", NULL, NULL, NULL, &h);
463 t = ltt_field_type(h.f1);
464 nb = ltt_type_element_number(t);
465
466 /*
467 tcs->irq_names = g_new(GQuark, nb);
468 for(i = 0 ; i < nb ; i++) {
469 tcs->irq_names[i] = g_quark_from_string(ltt_enum_string_get(t, i));
470 }
471 */
472
473 tcs->irq_names = g_new(GQuark, 256);
474 for(i = 0 ; i < 256 ; i++) {
475 g_string_printf(fe_name, "irq %d", i);
476 tcs->irq_names[i] = g_quark_from_string(fe_name->str);
477 }
478
479 g_string_free(fe_name, TRUE);
480 }
481
482
483 static void
484 free_name_tables(LttvTraceState *tcs)
485 {
486 g_free(tcs->eventtype_names);
487 g_free(tcs->syscall_names);
488 g_free(tcs->trap_names);
489 g_free(tcs->irq_names);
490 }
491
492
493 static void push_state(LttvTracefileState *tfs, LttvExecutionMode t,
494 guint state_id)
495 {
496 LttvExecutionState *es;
497
498 LttvProcessState *process = tfs->process;
499
500 guint depth = process->execution_stack->len;
501
502 g_array_set_size(process->execution_stack, depth + 1);
503 es = &g_array_index(process->execution_stack, LttvExecutionState, depth);
504 es->t = t;
505 es->n = state_id;
506 es->entry = es->change = tfs->parent.timestamp;
507 es->s = process->state->s;
508 process->state = es;
509 }
510
511
512 static void pop_state(LttvTracefileState *tfs, LttvExecutionMode t)
513 {
514 LttvProcessState *process = tfs->process;
515
516 guint depth = process->execution_stack->len - 1;
517
518 if(process->state->t != t){
519 g_warning("Different execution mode type (%d.%09d): ignore it\n",
520 tfs->parent.timestamp.tv_sec, tfs->parent.timestamp.tv_nsec);
521 g_warning("process state has %s when pop_int is %s\n",
522 g_quark_to_string(process->state->t),
523 g_quark_to_string(t));
524 g_warning("{ %u, %u, %s, %s }\n",
525 process->pid,
526 process->ppid,
527 g_quark_to_string(process->name),
528 g_quark_to_string(process->state->s));
529 return;
530 }
531
532 if(depth == 0){
533 g_warning("Trying to pop last state on stack (%d.%09d): ignore it\n",
534 tfs->parent.timestamp.tv_sec, tfs->parent.timestamp.tv_nsec);
535 return;
536 }
537
538 g_array_remove_index(process->execution_stack, depth);
539 depth--;
540 process->state = &g_array_index(process->execution_stack, LttvExecutionState,
541 depth);
542 process->state->change = tfs->parent.timestamp;
543 }
544
545
546 static LttvProcessState *create_process(LttvTracefileState *tfs,
547 LttvProcessState *parent, guint pid)
548 {
549 LttvProcessState *process = g_new(LttvProcessState, 1);
550
551 LttvExecutionState *es;
552
553 LttvTraceContext *tc;
554
555 LttvTraceState *tcs;
556
557 char buffer[128];
558
559 tcs = (LttvTraceState *)tc = tfs->parent.t_context;
560
561 g_hash_table_insert(tcs->processes, GUINT_TO_POINTER(pid), process);
562 process->pid = pid;
563
564 if(parent) {
565 process->ppid = parent->pid;
566 process->name = parent->name;
567 }
568 else {
569 process->ppid = 0;
570 process->name = LTTV_STATE_UNNAMED;
571 }
572
573 process->creation_time = tfs->parent.timestamp;
574 sprintf(buffer,"%d-%lu.%lu",pid, process->creation_time.tv_sec,
575 process->creation_time.tv_nsec);
576 process->pid_time = g_quark_from_string(buffer);
577 process->execution_stack = g_array_new(FALSE, FALSE,
578 sizeof(LttvExecutionState));
579 g_array_set_size(process->execution_stack, 1);
580 es = process->state = &g_array_index(process->execution_stack,
581 LttvExecutionState, 0);
582 es->t = LTTV_STATE_USER_MODE;
583 es->n = LTTV_STATE_SUBMODE_NONE;
584 es->entry = tfs->parent.timestamp;
585 es->change = tfs->parent.timestamp;
586 es->s = LTTV_STATE_WAIT_FORK;
587
588 return process;
589 }
590
591 static LttvProcessState *create_process_from_trace(LttvTraceState *tcs,
592 LttvProcessState *parent, guint pid)
593 {
594 LttvProcessState *process = g_new(LttvProcessState, 1);
595
596 LttvExecutionState *es;
597
598 LttvTraceContext *tc = (LttvTraceContext *)tcs;
599
600 char buffer[128];
601
602 g_hash_table_insert(tcs->processes, GUINT_TO_POINTER(pid), process);
603 process->pid = pid;
604
605 if(parent) {
606 process->ppid = parent->pid;
607 process->name = parent->name;
608 }
609 else {
610 process->ppid = 0;
611 process->name = LTTV_STATE_UNNAMED;
612 }
613
614 //FIXME timestamp should come from trace
615 process->creation_time.tv_sec = 0;
616 process->creation_time.tv_nsec = 0;
617 sprintf(buffer,"%d-%lu.%lu",pid, process->creation_time.tv_sec,
618 process->creation_time.tv_nsec);
619 process->pid_time = g_quark_from_string(buffer);
620 process->execution_stack = g_array_new(FALSE, FALSE,
621 sizeof(LttvExecutionState));
622 g_array_set_size(process->execution_stack, 1);
623 es = process->state = &g_array_index(process->execution_stack,
624 LttvExecutionState, 0);
625 es->t = LTTV_STATE_USER_MODE;
626 es->n = LTTV_STATE_SUBMODE_NONE;
627 //FIXME es->entry = tfs->parent.timestamp;
628 es->entry.tv_sec = 0;
629 es->entry.tv_nsec = 0;
630 //FIXME es->change = tfs->parent.timestamp;
631 es->change.tv_sec = 0;
632 es->change.tv_nsec = 0;
633 es->s = LTTV_STATE_WAIT_FORK;
634
635 return process;
636 }
637
638
639
640 LttvProcessState *lttv_state_find_process(LttvTracefileState *tfs,
641 guint pid)
642 {
643 LttvTraceState *ts =(LttvTraceState *)LTTV_TRACEFILE_CONTEXT(tfs)->t_context;
644 LttvProcessState *process = g_hash_table_lookup(ts->processes,
645 GUINT_TO_POINTER(pid));
646 if(process == NULL) process = create_process(tfs, NULL, pid);
647 return process;
648 }
649
650 LttvProcessState *lttv_state_find_process_from_trace(LttvTraceState *ts,
651 guint pid)
652 {
653 LttvProcessState *process = g_hash_table_lookup(ts->processes,
654 GUINT_TO_POINTER(pid));
655
656 if(process == NULL) process = create_process_from_trace(ts, NULL, pid);
657 return process;
658 }
659
660
661
662 static void exit_process(LttvTracefileState *tfs, LttvProcessState *process)
663 {
664 LttvTraceState *ts = LTTV_TRACE_STATE(tfs->parent.t_context);
665
666 g_hash_table_remove(ts->processes, GUINT_TO_POINTER(process->pid));
667 g_array_free(process->execution_stack, TRUE);
668 g_free(process);
669 }
670
671
672 static void free_process_state(gpointer key, gpointer value,gpointer user_data)
673 {
674 g_array_free(((LttvProcessState *)value)->execution_stack, TRUE);
675 g_free(value);
676 }
677
678
679 static void lttv_state_free_process_table(GHashTable *processes)
680 {
681 g_hash_table_foreach(processes, free_process_state, NULL);
682 g_hash_table_destroy(processes);
683 }
684
685
686 static gboolean syscall_entry(void *hook_data, void *call_data)
687 {
688 LttField *f = ((LttvTraceHook *)hook_data)->f1;
689
690 LttvTracefileState *s = (LttvTracefileState *)call_data;
691
692 LttvExecutionSubmode submode;
693
694 submode = ((LttvTraceState *)(s->parent.t_context))->syscall_names[
695 ltt_event_get_unsigned(s->parent.e, f)];
696 push_state(s, LTTV_STATE_SYSCALL, submode);
697 return FALSE;
698 }
699
700
701 static gboolean syscall_exit(void *hook_data, void *call_data)
702 {
703 LttvTracefileState *s = (LttvTracefileState *)call_data;
704
705 pop_state(s, LTTV_STATE_SYSCALL);
706 return FALSE;
707 }
708
709
710 static gboolean trap_entry(void *hook_data, void *call_data)
711 {
712 LttField *f = ((LttvTraceHook *)hook_data)->f1;
713
714 LttvTracefileState *s = (LttvTracefileState *)call_data;
715
716 LttvExecutionSubmode submode;
717
718 submode = ((LttvTraceState *)(s->parent.t_context))->trap_names[
719 ltt_event_get_unsigned(s->parent.e, f)];
720 push_state(s, LTTV_STATE_TRAP, submode);
721 return FALSE;
722 }
723
724
725 static gboolean trap_exit(void *hook_data, void *call_data)
726 {
727 LttvTracefileState *s = (LttvTracefileState *)call_data;
728
729 pop_state(s, LTTV_STATE_TRAP);
730 return FALSE;
731 }
732
733
734 static gboolean irq_entry(void *hook_data, void *call_data)
735 {
736 LttField *f = ((LttvTraceHook *)hook_data)->f1;
737
738 LttvTracefileState *s = (LttvTracefileState *)call_data;
739
740 LttvExecutionSubmode submode;
741
742 submode = ((LttvTraceState *)(s->parent.t_context))->irq_names[
743 ltt_event_get_unsigned(s->parent.e, f)];
744
745 /* Do something with the info about being in user or system mode when int? */
746 push_state(s, LTTV_STATE_IRQ, submode);
747 return FALSE;
748 }
749
750
751 static gboolean irq_exit(void *hook_data, void *call_data)
752 {
753 LttvTracefileState *s = (LttvTracefileState *)call_data;
754
755 pop_state(s, LTTV_STATE_IRQ);
756 return FALSE;
757 }
758
759
760 static gboolean schedchange(void *hook_data, void *call_data)
761 {
762 LttvTraceHook *h = (LttvTraceHook *)hook_data;
763
764 LttvTracefileState *s = (LttvTracefileState *)call_data;
765
766 guint pid_in, pid_out, state_out;
767
768 pid_in = ltt_event_get_unsigned(s->parent.e, h->f1);
769 pid_out = ltt_event_get_unsigned(s->parent.e, h->f2);
770 state_out = ltt_event_get_unsigned(s->parent.e, h->f3);
771
772 if(s->process != NULL) {
773
774 if(state_out == 0) s->process->state->s = LTTV_STATE_WAIT_CPU;
775 else if(s->process->state->s == LTTV_STATE_EXIT)
776 exit_process(s, s->process);
777 else s->process->state->s = LTTV_STATE_WAIT;
778
779 if(s->process->pid == 0)
780 s->process->pid = pid_out;
781
782 s->process->state->change = s->parent.timestamp;
783 }
784 s->process = lttv_state_find_process(s, pid_in);
785 s->process->state->s = LTTV_STATE_RUN;
786 s->process->state->change = s->parent.timestamp;
787 return FALSE;
788 }
789
790
791 static gboolean process_fork(void *hook_data, void *call_data)
792 {
793 LttField *f = ((LttvTraceHook *)hook_data)->f1;
794
795 LttvTracefileState *s = (LttvTracefileState *)call_data;
796
797 guint child_pid;
798
799 child_pid = ltt_event_get_unsigned(s->parent.e, f);
800 create_process(s, s->process, child_pid);
801 return FALSE;
802 }
803
804
805 static gboolean process_exit(void *hook_data, void *call_data)
806 {
807 LttvTracefileState *s = (LttvTracefileState *)call_data;
808
809 if(s->process != NULL) {
810 s->process->state->s = LTTV_STATE_EXIT;
811 }
812 return FALSE;
813 }
814
815
816 void lttv_state_add_event_hooks(LttvTracesetState *self)
817 {
818 LttvTraceset *traceset = self->parent.ts;
819
820 guint i, j, k, nb_trace, nb_control, nb_per_cpu, nb_tracefile;
821
822 LttvTraceState *ts;
823
824 LttvTracefileState *tfs;
825
826 GArray *hooks;
827
828 LttvTraceHook hook;
829
830 LttvAttributeValue val;
831
832 nb_trace = lttv_traceset_number(traceset);
833 for(i = 0 ; i < nb_trace ; i++) {
834 ts = (LttvTraceState *)self->parent.traces[i];
835
836 /* Find the eventtype id for the following events and register the
837 associated by id hooks. */
838
839 hooks = g_array_new(FALSE, FALSE, sizeof(LttvTraceHook));
840 g_array_set_size(hooks, 9);
841
842 lttv_trace_find_hook(ts->parent.t, "core","syscall_entry","syscall_id",
843 NULL, NULL, syscall_entry, &g_array_index(hooks, LttvTraceHook, 0));
844
845 lttv_trace_find_hook(ts->parent.t, "core", "syscall_exit", NULL, NULL,
846 NULL, syscall_exit, &g_array_index(hooks, LttvTraceHook, 1));
847
848 lttv_trace_find_hook(ts->parent.t, "core", "trap_entry", "trap_id",
849 NULL, NULL, trap_entry, &g_array_index(hooks, LttvTraceHook, 2));
850
851 lttv_trace_find_hook(ts->parent.t, "core", "trap_exit", NULL, NULL, NULL,
852 trap_exit, &g_array_index(hooks, LttvTraceHook, 3));
853
854 lttv_trace_find_hook(ts->parent.t, "core", "irq_entry", "irq_id", NULL,
855 NULL, irq_entry, &g_array_index(hooks, LttvTraceHook, 4));
856
857 lttv_trace_find_hook(ts->parent.t, "core", "irq_exit", NULL, NULL, NULL,
858 irq_exit, &g_array_index(hooks, LttvTraceHook, 5));
859
860 lttv_trace_find_hook(ts->parent.t, "core", "schedchange", "in", "out",
861 "out_state", schedchange, &g_array_index(hooks, LttvTraceHook, 6));
862
863 lttv_trace_find_hook(ts->parent.t, "core", "process_fork", "child_pid",
864 NULL, NULL, process_fork, &g_array_index(hooks, LttvTraceHook, 7));
865
866 lttv_trace_find_hook(ts->parent.t, "core", "process_exit", NULL, NULL,
867 NULL, process_exit, &g_array_index(hooks, LttvTraceHook, 8));
868
869 /* Add these hooks to each before_event_by_id hooks list */
870
871 nb_control = ltt_trace_control_tracefile_number(ts->parent.t);
872 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(ts->parent.t);
873 nb_tracefile = nb_control + nb_per_cpu;
874 for(j = 0 ; j < nb_tracefile ; j++) {
875 if(j < nb_control) {
876 tfs = LTTV_TRACEFILE_STATE(ts->parent.control_tracefiles[j]);
877 }
878 else {
879 tfs = LTTV_TRACEFILE_STATE(ts->parent.per_cpu_tracefiles[j-nb_control]);
880 }
881
882 for(k = 0 ; k < hooks->len ; k++) {
883 hook = g_array_index(hooks, LttvTraceHook, k);
884 lttv_hooks_add(lttv_hooks_by_id_find(tfs->parent.after_event_by_id,
885 hook.id), hook.h, &g_array_index(hooks, LttvTraceHook, k));
886 }
887 }
888 lttv_attribute_find(self->parent.a, LTTV_STATE_HOOKS, LTTV_POINTER, &val);
889 *(val.v_pointer) = hooks;
890 }
891 }
892
893
894 void lttv_state_remove_event_hooks(LttvTracesetState *self)
895 {
896 LttvTraceset *traceset = self->parent.ts;
897
898 guint i, j, k, nb_trace, nb_control, nb_per_cpu, nb_tracefile;
899
900 LttvTraceState *ts;
901
902 LttvTracefileState *tfs;
903
904 GArray *hooks;
905
906 LttvTraceHook hook;
907
908 LttvAttributeValue val;
909
910 nb_trace = lttv_traceset_number(traceset);
911 for(i = 0 ; i < nb_trace ; i++) {
912 ts = LTTV_TRACE_STATE(self->parent.traces[i]);
913 lttv_attribute_find(self->parent.a, LTTV_STATE_HOOKS, LTTV_POINTER, &val);
914 hooks = *(val.v_pointer);
915
916 /* Add these hooks to each before_event_by_id hooks list */
917
918 nb_control = ltt_trace_control_tracefile_number(ts->parent.t);
919 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(ts->parent.t);
920 nb_tracefile = nb_control + nb_per_cpu;
921 for(j = 0 ; j < nb_tracefile ; j++) {
922 if(j < nb_control) {
923 tfs = LTTV_TRACEFILE_STATE(ts->parent.control_tracefiles[j]);
924 }
925 else {
926 tfs = LTTV_TRACEFILE_STATE(ts->parent.per_cpu_tracefiles[j-nb_control]);
927 }
928
929 for(k = 0 ; k < hooks->len ; k++) {
930 hook = g_array_index(hooks, LttvTraceHook, k);
931 lttv_hooks_remove_data(
932 lttv_hooks_by_id_find(tfs->parent.after_event_by_id,
933 hook.id), hook.h, &g_array_index(hooks, LttvTraceHook, k));
934 }
935 }
936 g_array_free(hooks, TRUE);
937 }
938 }
939
940
941 static gboolean block_end(void *hook_data, void *call_data)
942 {
943 LttvTracefileState *tfcs = (LttvTracefileState *)call_data;
944
945 LttvTraceState *tcs = (LttvTraceState *)(tfcs->parent.t_context);
946
947 LttEventPosition *ep = ltt_event_position_new();
948
949 guint nb_block, nb_event;
950
951 LttTracefile *tf;
952
953 LttvAttribute *saved_states_tree, *saved_state_tree;
954
955 LttvAttributeValue value;
956
957 ltt_event_position(tfcs->parent.e, ep);
958
959 ltt_event_position_get(ep, &nb_block, &nb_event, &tf);
960 tcs->nb_event += nb_event - tfcs->saved_position;
961 tfcs->saved_position = 0;
962 if(tcs->nb_event >= tcs->save_interval) {
963 saved_states_tree = lttv_attribute_find_subdir(tcs->parent.t_a,
964 LTTV_STATE_SAVED_STATES);
965 saved_state_tree = g_object_new(LTTV_ATTRIBUTE_TYPE, NULL);
966 value = lttv_attribute_add(saved_states_tree,
967 lttv_attribute_get_number(saved_states_tree), LTTV_GOBJECT);
968 *(value.v_gobject) = (GObject *)saved_state_tree;
969 value = lttv_attribute_add(saved_state_tree, LTTV_STATE_TIME, LTTV_TIME);
970 *(value.v_time) = tfcs->parent.timestamp;
971 lttv_state_save(tcs, saved_state_tree);
972 tcs->nb_event = 0;
973 }
974 return FALSE;
975 }
976
977
978 void lttv_state_save_add_event_hooks(LttvTracesetState *self)
979 {
980 LttvTraceset *traceset = self->parent.ts;
981
982 guint i, j, k, nb_trace, nb_control, nb_per_cpu, nb_tracefile;
983
984 LttvTraceState *ts;
985
986 LttvTracefileState *tfs;
987
988 LttvTraceHook hook;
989
990 nb_trace = lttv_traceset_number(traceset);
991 for(i = 0 ; i < nb_trace ; i++) {
992 ts = (LttvTraceState *)self->parent.traces[i];
993 lttv_trace_find_hook(ts->parent.t, "core","block_end",NULL,
994 NULL, NULL, block_end, &hook);
995
996 nb_control = ltt_trace_control_tracefile_number(ts->parent.t);
997 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(ts->parent.t);
998 nb_tracefile = nb_control + nb_per_cpu;
999 for(j = 0 ; j < nb_tracefile ; j++) {
1000 if(j < nb_control) {
1001 tfs = LTTV_TRACEFILE_STATE(ts->parent.control_tracefiles[j]);
1002 }
1003 else {
1004 tfs =LTTV_TRACEFILE_STATE(ts->parent.per_cpu_tracefiles[j-nb_control]);
1005 }
1006
1007 lttv_hooks_add(lttv_hooks_by_id_find(tfs->parent.after_event_by_id,
1008 hook.id), hook.h, NULL);
1009 }
1010 }
1011 }
1012
1013
1014 void lttv_state_save_remove_event_hooks(LttvTracesetState *self)
1015 {
1016 LttvTraceset *traceset = self->parent.ts;
1017
1018 guint i, j, k, nb_trace, nb_control, nb_per_cpu, nb_tracefile;
1019
1020 LttvTraceState *ts;
1021
1022 LttvTracefileState *tfs;
1023
1024 LttvTraceHook hook;
1025
1026 nb_trace = lttv_traceset_number(traceset);
1027 for(i = 0 ; i < nb_trace ; i++) {
1028 ts = LTTV_TRACE_STATE(self->parent.traces[i]);
1029 lttv_trace_find_hook(ts->parent.t, "core","block_end",NULL,
1030 NULL, NULL, block_end, &hook);
1031
1032 nb_control = ltt_trace_control_tracefile_number(ts->parent.t);
1033 nb_per_cpu = ltt_trace_per_cpu_tracefile_number(ts->parent.t);
1034 nb_tracefile = nb_control + nb_per_cpu;
1035 for(j = 0 ; j < nb_tracefile ; j++) {
1036 if(j < nb_control) {
1037 tfs = LTTV_TRACEFILE_STATE(ts->parent.control_tracefiles[j]);
1038 }
1039 else {
1040 tfs =LTTV_TRACEFILE_STATE(ts->parent.per_cpu_tracefiles[j-nb_control]);
1041 }
1042
1043 lttv_hooks_remove_data(lttv_hooks_by_id_find(
1044 tfs->parent.after_event_by_id, hook.id), hook.h, NULL);
1045 }
1046 }
1047 }
1048
1049
1050 void lttv_state_traceset_seek_time_closest(LttvTracesetState *self, LttTime t)
1051 {
1052 LttvTraceset *traceset = self->parent.ts;
1053
1054 guint i, j, nb_trace, nb_saved_state;
1055
1056 int min_pos, mid_pos, max_pos;
1057
1058 LttvTraceState *tcs;
1059
1060 LttvAttributeValue value;
1061
1062 LttvAttributeType type;
1063
1064 LttvAttributeName name;
1065
1066 LttvAttribute *saved_states_tree, *saved_state_tree, *closest_tree;
1067
1068 nb_trace = lttv_traceset_number(traceset);
1069 for(i = 0 ; i < nb_trace ; i++) {
1070 tcs = (LttvTraceState *)self->parent.traces[i];
1071
1072 if(tcs->recompute_state_in_seek) {
1073 if(tcs->saved_state_available) {
1074 saved_states_tree = lttv_attribute_find_subdir(tcs->parent.t_a,
1075 LTTV_STATE_SAVED_STATES);
1076 min_pos = -1;
1077 max_pos = lttv_attribute_get_number(saved_states_tree) - 1;
1078 mid_pos = max_pos / 2;
1079 while(min_pos < max_pos) {
1080 type = lttv_attribute_get(saved_states_tree, mid_pos, &name, &value);
1081 g_assert(type == LTTV_GOBJECT);
1082 saved_state_tree = *((LttvAttribute **)(value.v_gobject));
1083 type = lttv_attribute_get_by_name(saved_state_tree, LTTV_STATE_TIME,
1084 &value);
1085 g_assert(type == LTTV_TIME);
1086 if(ltt_time_compare(*(value.v_time), t) < 0) {
1087 min_pos = mid_pos;
1088 closest_tree = saved_state_tree;
1089 }
1090 else max_pos = mid_pos - 1;
1091
1092 mid_pos = (min_pos + max_pos + 1) / 2;
1093 }
1094
1095 /* restore the closest earlier saved state */
1096 if(min_pos != -1) lttv_state_restore(tcs, closest_tree);
1097
1098 }
1099 /* There is no saved state yet we want to have it. Restart at T0 */
1100 else {
1101 restore_init_state(tcs);
1102 lttv_process_trace_seek_time(&(tcs->parent), ltt_time_zero);
1103 }
1104 }
1105 /* We want to seek quickly without restoring/updating the state */
1106 else {
1107 restore_init_state(tcs);
1108 lttv_process_trace_seek_time(&(tcs->parent), t);
1109 }
1110 }
1111 }
1112
1113
1114 static void
1115 traceset_state_instance_init (GTypeInstance *instance, gpointer g_class)
1116 {
1117 }
1118
1119
1120 static void
1121 traceset_state_finalize (LttvTracesetState *self)
1122 {
1123 G_OBJECT_CLASS(g_type_class_peek(LTTV_TRACESET_CONTEXT_TYPE))->
1124 finalize(G_OBJECT(self));
1125 }
1126
1127
1128 static void
1129 traceset_state_class_init (LttvTracesetContextClass *klass)
1130 {
1131 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1132
1133 gobject_class->finalize = (void (*)(GObject *self)) traceset_state_finalize;
1134 klass->init = (void (*)(LttvTracesetContext *self, LttvTraceset *ts))init;
1135 klass->fini = (void (*)(LttvTracesetContext *self))fini;
1136 klass->new_traceset_context = new_traceset_context;
1137 klass->new_trace_context = new_trace_context;
1138 klass->new_tracefile_context = new_tracefile_context;
1139 }
1140
1141
1142 GType
1143 lttv_traceset_state_get_type(void)
1144 {
1145 static GType type = 0;
1146 if (type == 0) {
1147 static const GTypeInfo info = {
1148 sizeof (LttvTracesetStateClass),
1149 NULL, /* base_init */
1150 NULL, /* base_finalize */
1151 (GClassInitFunc) traceset_state_class_init, /* class_init */
1152 NULL, /* class_finalize */
1153 NULL, /* class_data */
1154 sizeof (LttvTracesetContext),
1155 0, /* n_preallocs */
1156 (GInstanceInitFunc) traceset_state_instance_init /* instance_init */
1157 };
1158
1159 type = g_type_register_static (LTTV_TRACESET_CONTEXT_TYPE, "LttvTracesetStateType",
1160 &info, 0);
1161 }
1162 return type;
1163 }
1164
1165
1166 static void
1167 trace_state_instance_init (GTypeInstance *instance, gpointer g_class)
1168 {
1169 }
1170
1171
1172 static void
1173 trace_state_finalize (LttvTraceState *self)
1174 {
1175 G_OBJECT_CLASS(g_type_class_peek(LTTV_TRACE_CONTEXT_TYPE))->
1176 finalize(G_OBJECT(self));
1177 }
1178
1179
1180 static void
1181 trace_state_class_init (LttvTraceStateClass *klass)
1182 {
1183 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1184
1185 gobject_class->finalize = (void (*)(GObject *self)) trace_state_finalize;
1186 klass->state_save = state_save;
1187 klass->state_restore = state_restore;
1188 klass->state_saved_free = state_saved_free;
1189 }
1190
1191
1192 GType
1193 lttv_trace_state_get_type(void)
1194 {
1195 static GType type = 0;
1196 if (type == 0) {
1197 static const GTypeInfo info = {
1198 sizeof (LttvTraceStateClass),
1199 NULL, /* base_init */
1200 NULL, /* base_finalize */
1201 (GClassInitFunc) trace_state_class_init, /* class_init */
1202 NULL, /* class_finalize */
1203 NULL, /* class_data */
1204 sizeof (LttvTraceState),
1205 0, /* n_preallocs */
1206 (GInstanceInitFunc) trace_state_instance_init /* instance_init */
1207 };
1208
1209 type = g_type_register_static (LTTV_TRACE_CONTEXT_TYPE,
1210 "LttvTraceStateType", &info, 0);
1211 }
1212 return type;
1213 }
1214
1215
1216 static void
1217 tracefile_state_instance_init (GTypeInstance *instance, gpointer g_class)
1218 {
1219 }
1220
1221
1222 static void
1223 tracefile_state_finalize (LttvTracefileState *self)
1224 {
1225 G_OBJECT_CLASS(g_type_class_peek(LTTV_TRACEFILE_CONTEXT_TYPE))->
1226 finalize(G_OBJECT(self));
1227 }
1228
1229
1230 static void
1231 tracefile_state_class_init (LttvTracefileStateClass *klass)
1232 {
1233 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1234
1235 gobject_class->finalize = (void (*)(GObject *self)) tracefile_state_finalize;
1236 }
1237
1238
1239 GType
1240 lttv_tracefile_state_get_type(void)
1241 {
1242 static GType type = 0;
1243 if (type == 0) {
1244 static const GTypeInfo info = {
1245 sizeof (LttvTracefileStateClass),
1246 NULL, /* base_init */
1247 NULL, /* base_finalize */
1248 (GClassInitFunc) tracefile_state_class_init, /* class_init */
1249 NULL, /* class_finalize */
1250 NULL, /* class_data */
1251 sizeof (LttvTracefileState),
1252 0, /* n_preallocs */
1253 (GInstanceInitFunc) tracefile_state_instance_init /* instance_init */
1254 };
1255
1256 type = g_type_register_static (LTTV_TRACEFILE_CONTEXT_TYPE,
1257 "LttvTracefileStateType", &info, 0);
1258 }
1259 return type;
1260 }
1261
1262
1263 void lttv_state_init(int argc, char **argv)
1264 {
1265 LTTV_STATE_UNNAMED = g_quark_from_string("unnamed");
1266 LTTV_STATE_MODE_UNKNOWN = g_quark_from_string("unknown execution mode");
1267 LTTV_STATE_USER_MODE = g_quark_from_string("user mode");
1268 LTTV_STATE_WAIT_FORK = g_quark_from_string("wait fork");
1269 LTTV_STATE_SYSCALL = g_quark_from_string("system call");
1270 LTTV_STATE_TRAP = g_quark_from_string("trap");
1271 LTTV_STATE_IRQ = g_quark_from_string("irq");
1272 LTTV_STATE_SUBMODE_UNKNOWN = g_quark_from_string("unknown submode");
1273 LTTV_STATE_SUBMODE_NONE = g_quark_from_string("(no submode)");
1274 LTTV_STATE_WAIT_CPU = g_quark_from_string("wait for cpu");
1275 LTTV_STATE_EXIT = g_quark_from_string("exiting");
1276 LTTV_STATE_WAIT = g_quark_from_string("wait for I/O");
1277 LTTV_STATE_RUN = g_quark_from_string("running");
1278 LTTV_STATE_TRACEFILES = g_quark_from_string("tracefiles");
1279 LTTV_STATE_PROCESSES = g_quark_from_string("processes");
1280 LTTV_STATE_PROCESS = g_quark_from_string("process");
1281 LTTV_STATE_EVENT = g_quark_from_string("event");
1282 LTTV_STATE_SAVED_STATES = g_quark_from_string("saved states");
1283 LTTV_STATE_TIME = g_quark_from_string("time");
1284 LTTV_STATE_HOOKS = g_quark_from_string("saved state hooks");
1285 }
1286
1287 void lttv_state_destroy()
1288 {
1289 }
1290
1291
1292
1293
This page took 0.056063 seconds and 4 git commands to generate.