1 /* This file is part of the Linux Trace Toolkit viewer
2 * Copyright (C) 2003-2004 Xiangxiu Yang
3 * 2005 Mathieu Desnoyers
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License Version 2 as
7 * published by the Free Software Foundation;
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
28 #include <sys/types.h>
36 #include "ltt-private.h"
37 #include <ltt/facility.h>
45 /* search for the (named) type in the table, if it does not exist
47 LttType
* lookup_named_type(LttFacility
*fac
, type_descriptor_t
* td
);
49 /* construct directed acyclic graph for types, and tree for fields */
50 void construct_types_and_fields(LttFacility
* fac
, type_descriptor_t
* td
,
53 /* generate the facility according to the events belongin to it */
54 void generateFacility(LttFacility
* f
, facility_t
* fac
,
57 /* functions to release the memory occupied by a facility */
58 void freeFacility(LttFacility
* facility
);
59 void freeEventtype(LttEventType
* evType
);
60 void freeLttType(LttType
** type
);
61 void freeLttField(LttField
* fld
);
62 void freeLttNamedType(LttType
* type
);
65 /*****************************************************************************
67 * ltt_facility_open : open facilities
69 * t : the trace containing the facilities
70 * pathname : the path name of the facility
72 * Open the facility corresponding to the right checksum.
74 *returns 0 on success, 1 on error.
75 ****************************************************************************/
77 int ltt_facility_open(LttFacility
*f
, LttTrace
* t
, gchar
* pathname
)
83 unsigned long checksum
;
84 gchar buffer
[BUFFER_SIZE
];
85 gboolean generated
= FALSE
;
87 in
.buffer
= &(buffer
[0]);
89 in
.error
= error_callback
;
93 in
.fp
= fopen(in
.name
, "r");
95 g_warning("cannot open facility description file %s",
102 token
= getToken(&in
);
103 if(in
.type
== ENDFILE
) break;
105 if(g_ascii_strcasecmp(token
, "<")) in
.error(&in
,"not a facility file");
106 token
= getName(&in
);
108 if(g_ascii_strcasecmp("facility",token
) == 0) {
109 fac
= g_new(facility_t
, 1);
111 fac
->description
= NULL
;
112 sequence_init(&(fac
->events
));
113 table_init(&(fac
->named_types
));
114 sequence_init(&(fac
->unnamed_types
));
116 parseFacility(&in
, fac
);
118 //check if any namedType is not defined
119 checkNamedTypesImplemented(&fac
->named_types
);
121 generateChecksum(fac
->name
, &checksum
, &fac
->events
);
123 if(checksum
== f
->checksum
) {
124 generateFacility(f
, fac
, checksum
);
130 g_free(fac
->description
);
131 freeEvents(&fac
->events
);
132 sequence_dispose(&fac
->events
);
133 freeNamedType(&fac
->named_types
);
134 table_dispose(&fac
->named_types
);
135 freeTypes(&fac
->unnamed_types
);
136 sequence_dispose(&fac
->unnamed_types
);
138 if(generated
) break; /* use the first good match */
141 g_warning("facility token was expected in file %s", in
.name
);
152 g_warning("Cannot find facility %s, checksum 0x%X",
153 g_quark_to_string(f
->name
), f
->checksum
);
161 /*****************************************************************************
163 * generateFacility : generate facility, internal function
165 * facility : LttFacilty structure
166 * fac : facility structure
167 * checksum : checksum of the facility
168 ****************************************************************************/
170 void generateFacility(LttFacility
*f
, facility_t
*fac
, guint32 checksum
)
172 char * facilityName
= fac
->name
;
173 sequence_t
* events
= &fac
->events
;
176 table_t
*named_types
= &fac
->named_types
;
178 g_assert(f
->name
== g_quark_from_string(facilityName
));
179 g_assert(f
->checksum
== checksum
);
181 //f->event_number = events->position;
183 //initialize inner structures
184 f
->events
= g_array_sized_new (FALSE
, TRUE
, sizeof(LttEventType
),
186 //f->events = g_new(LttEventType*,f->event_number);
187 f
->events
= g_array_set_size(f
->events
, events
->position
);
189 g_datalist_init(&f
->events_by_name
);
190 g_datalist_init(&f
->named_types
);
192 /* The first day, he created the named types */
194 for(i
=0; i
<named_types
->keys
.position
; i
++) {
195 GQuark name
= g_quark_from_string((char*)named_types
->keys
.array
[i
]);
196 type_descriptor_t
*td
= (type_descriptor_t
*)named_types
->values
.array
[i
];
198 /* Create the type */
199 type
= g_new(LttType
,1);
200 type
->type_name
= name
;
201 type
->type_class
= td
->type
;
202 if(td
->fmt
) type
->fmt
= g_strdup(td
->fmt
);
203 else type
->fmt
= NULL
;
204 type
->size
= td
->size
;
205 type
->enum_strings
= NULL
;
206 type
->element_type
= NULL
;
207 type
->element_number
= 0;
209 construct_types_and_fields(type
, td
, NULL
, NULL
, ...);
211 g_datalist_id_set_data_full(&fac
->named_types
, name
,
212 type
, (GDestroyNotify
)freeLttNamedType
);
216 /* The second day, he created the event fields and types */
217 //for each event, construct field and type acyclic graph
218 for(i
=0;i
<events
->position
;i
++){
219 event_t parser_event
= (event_t
*)events
->array
[i
];
220 LttEventType
*event_type
= &g_array_index(f
->events
, LttEventType
, i
);
223 g_quark_from_string(parser_event
->name
);
225 g_datalist_id_set_data(&f
->events_by_name
, event_type
->name
,
228 event_type
->description
=
229 g_strdup(parser_event
->description
);
231 event_type
->index
= i
;
232 event_type
->facility
= f
;
234 event_type
->fields
= g_array_sized_new(FALSE
, TRUE
,
235 sizeof(LttField
), parser_event
->fields
.position
);
237 g_array_set_size(event_type
->fields
, parser_event
->fields
.position
);
238 g_datalist_init(&event_type
->fields_by_name
);
240 for(j
=0; j
<parser_event
->fields
.position
; j
++) {
241 LttField
*field
= &g_array_index(event_type
->fields
, LttField
, j
);
242 field_t
*parser_field
= (field_t
*)parser_event
->fields
.array
[j
];
244 construct_types_and_fields(NULL
, NULL
, field
, parser_field
, ...);
245 g_datalist_id_set_data(&event_type
->fields_by_name
,
251 /* What about 2 days weeks ? */
255 /*****************************************************************************
257 * construct_types_and_fields : construct field tree and type graph,
258 * internal recursion function
260 * fac : facility struct
261 * field : destination lttv field
262 * fld : source parser field
263 ****************************************************************************/
266 //make the change for arrays and sequences
267 //no more root field. -> change this for an array of fields.
268 // Compute the field size here.
269 // Flag fields as "VARIABLE OFFSET" or "FIXED OFFSET" : as soon as
270 // a field with a variable size is found, all the following fields must
271 // be flagged with "VARIABLE OFFSET", this will be done by the offset
275 void construct_fields(LttFacility
*fac
,
280 type_descriptor_t
*td
;
282 field
->name
= g_quark_from_string(fld
->name
);
283 if(fld
->description
) {
284 len
= strlen(fld
->description
);
285 field
->description
= g_new(gchar
, len
+1);
286 strcpy(field
->description
, fld
->description
);
288 field
->dynamic_offsets
= NULL
;
289 type
= &field
->field_type
;
292 type
->enum_map
= NULL
;
294 type
->fields_by_name
= NULL
;
298 type
->type_class
= LTT_INT_FIXED
;
299 type
->size
= td
->size
;
302 type
->type_class
= LTT_UINT_FIXED
;
303 type
->size
= td
->size
;
306 type
->type_class
= LTT_POINTER
;
307 type
->size
= fac
->pointer_size
;
310 type
->type_class
= LTT_CHAR
;
311 type
->size
= td
->size
;
314 type
->type_class
= LTT_UCHAR
;
315 type
->size
= td
->size
;
318 type
->type_class
= LTT_SHORT
;
319 type
->size
= td
->size
;
322 type
->type_class
= LTT_USHORT
;
323 type
->size
= td
->size
;
326 type
->type_class
= LTT_INT
;
327 type
->size
= fac
->int_size
;
330 type
->type_class
= LTT_UINT
;
331 type
->size
= fac
->int_size
;
334 type
->type_class
= LTT_LONG
;
335 type
->size
= fac
->long_size
;
338 type
->type_class
= LTT_ULONG
;
339 type
->size
= fac
->long_size
;
342 type
->type_class
= LTT_SIZE_T
;
343 type
->size
= fac
->size_t_size
;
346 type
->type_class
= LTT_SSIZE_T
;
347 type
->size
= fac
->size_t_size
;
350 type
->type_class
= LTT_OFF_T
;
351 type
->size
= fac
->size_t_size
;
354 type
->type_class
= LTT_FLOAT
;
355 type
->size
= td
->size
;
358 type
->type_class
= LTT_STRING
;
362 type
->type_class
= LTT_ENUM
;
363 type
->size
= fac
->int_size
;
366 g_datalist_init(&type
->enum_map
);
367 for(i
=0; i
<td
->labels
.position
; i
++) {
368 GQuark key
= g_quark_from_string((char*)td
->labels
.array
[i
]);
369 int *src
= (int*)td
->labels_values
.array
[i
];
370 /* it's always ok to cast a int to a pointer type */
371 g_datalist_id_set_data(&type
->enum_map
, key
, (gpointer
)*src
);
376 type
->type_class
= LTT_ARRAY
;
377 type
->size
= td
->size
;
378 type
->fields
= g_array_sized_new(FALSE
, TRUE
, sizeof(LttField
),
379 td
->fields
.position
);
380 type
->fields
= g_array_set_size(type
->fields
, td
->fields
.position
);
384 for(i
=0; i
<td
->fields
.position
; i
++) {
385 field_t
*schild
= (field_t
*)td
->fields
.array
[i
];
386 LttField
*dchild
= &g_array_index(type
->fields
, LttField
, i
);
388 construct_fields(fac
, dchild
, schild
);
393 type
->type_class
= LTT_SEQUENCE
;
395 type
->fields
= g_array_sized_new(FALSE
, TRUE
, sizeof(LttField
),
396 td
->fields
.position
);
397 type
->fields
= g_array_set_size(type
->fields
, td
->fields
.position
);
401 for(i
=0; i
<td
->fields
.position
; i
++) {
402 field_t
*schild
= (field_t
*)td
->fields
.array
[i
];
403 LttField
*dchild
= &g_array_index(type
->fields
, LttField
, i
);
405 construct_fields(fac
, dchild
, schild
);
410 type
->type_class
= LTT_STRUCT
;
411 type
->size
= 0; // Size not calculated by the parser.
412 type
->fields
= g_array_sized_new(FALSE
, TRUE
, sizeof(LttField
),
413 td
->fields
.position
);
414 type
->fields
= g_array_set_size(type
->fields
, td
->fields
.position
);
415 g_datalist_init(&type
->fields_by_name
);
419 for(i
=0; i
<td
->fields
.position
; i
++) {
420 field_t
*schild
= (field_t
*)td
->fields
.array
[i
];
421 LttField
*dchild
= &g_array_index(type
->fields
, LttField
, i
);
423 construct_fields(fac
, dchild
, schild
);
424 g_datalist_id_set_data(&type
->fields_by_name
,
431 type
->type_class
= LTT_UNION
;
432 type
->size
= 0; // Size not calculated by the parser.
433 type
->fields
= g_array_sized_new(FALSE
, TRUE
, sizeof(LttField
),
434 td
->fields
.position
);
435 type
->fields
= g_array_set_size(type
->fields
, td
->fields
.position
);
436 g_datalist_init(&type
->fields_by_name
);
440 for(i
=0; i
<td
->fields
.position
; i
++) {
441 field_t
*schild
= (field_t
*)td
->fields
.array
[i
];
442 LttField
*dchild
= &g_array_index(type
->fields
, LttField
, i
);
444 construct_fields(fac
, dchild
, schild
);
445 g_datalist_id_set_data(&type
->fields_by_name
,
453 g_error("construct_fields : unknown type");
456 field
->field_size
= type
->size
;
458 /* Put the fields as "variable" offset to root first. Then,
459 * the offset precomputation will only have to set the FIELD_FIXED until
460 * it reaches the first variable length field, then stop.
462 field
->fixed_root
= FIELD_VARIABLE
;
465 len
= strlen(td
->fmt
);
466 type
->fmt
= g_new(gchar
, len
+1);
467 strcpy(type
->fmt
, td
->fmt
);
474 void construct_types_and_fields(LttFacility
* fac
, type_descriptor_t
* td
,
478 type_descriptor_t
* tmpTd
;
484 fld
->field_type
->size
= td
->size
;
492 fld
->field_type
->size
= 0;
495 fld
->field_type
->size
= 0;
498 fld
->field_type
->element_number
= td
->labels
.position
;
499 fld
->field_type
->enum_strings
= g_new(GQuark
,td
->labels
.position
);
500 for(i
=0;i
<td
->labels
.position
;i
++){
501 fld
->field_type
->enum_strings
[i
]
502 = g_quark_from_string(((char*)(td
->labels
.array
[i
])));
504 fld
->field_type
->size
= td
->size
;
508 fld
->field_type
->element_number
= (unsigned)td
->size
;
510 fld
->field_type
->element_type
= g_new(LttType
*,1);
511 tmpTd
= td
->nested_type
;
512 fld
->field_type
->element_type
[0] = lookup_named_type(fac
, tmpTd
);
513 fld
->child
= g_new(LttField
*, 1);
514 fld
->child
[0] = g_new(LttField
, 1);
516 fld
->child
[0]->field_type
= fld
->field_type
->element_type
[0];
517 fld
->child
[0]->offset_root
= 0;
518 fld
->child
[0]->fixed_root
= FIELD_UNKNOWN
;
519 fld
->child
[0]->offset_parent
= 0;
520 fld
->child
[0]->fixed_parent
= FIELD_UNKNOWN
;
521 fld
->child
[0]->field_size
= 0;
522 fld
->child
[0]->fixed_size
= FIELD_UNKNOWN
;
523 fld
->child
[0]->parent
= fld
;
524 fld
->child
[0]->child
= NULL
;
525 fld
->child
[0]->current_element
= 0;
526 construct_types_and_fields(fac
, tmpTd
, fld
->child
[0]);
531 fld
->field_type
->element_number
= td
->fields
.position
;
533 g_assert(fld
->field_type
->element_type
== NULL
);
534 fld
->field_type
->element_type
= g_new(LttType
*, td
->fields
.position
);
536 fld
->child
= g_new(LttField
*, td
->fields
.position
);
537 for(i
=0;i
<td
->fields
.position
;i
++){
538 tmpTd
= ((field_t
*)(td
->fields
.array
[i
]))->type
;
540 fld
->field_type
->element_type
[i
] = lookup_named_type(fac
, tmpTd
);
541 fld
->child
[i
] = g_new(LttField
,1);
543 // fld->child[i]->field_pos = i;
544 fld
->child
[i
]->field_type
= fld
->field_type
->element_type
[i
];
546 fld
->child
[i
]->field_type
->element_name
547 = g_quark_from_string(((field_t
*)(td
->fields
.array
[i
]))->name
);
549 fld
->child
[i
]->offset_root
= 0;
550 fld
->child
[i
]->fixed_root
= FIELD_UNKNOWN
;
551 fld
->child
[i
]->offset_parent
= 0;
552 fld
->child
[i
]->fixed_parent
= FIELD_UNKNOWN
;
553 fld
->child
[i
]->field_size
= 0;
554 fld
->child
[i
]->fixed_size
= FIELD_UNKNOWN
;
555 fld
->child
[i
]->parent
= fld
;
556 fld
->child
[i
]->child
= NULL
;
557 fld
->child
[i
]->current_element
= 0;
558 construct_types_and_fields(fac
, tmpTd
, fld
->child
[i
]);
563 g_error("construct_types_and_fields : unknown type");
572 void construct_types_and_fields(LttFacility
* fac
, type_descriptor
* td
,
576 type_descriptor
* tmpTd
;
578 // if(td->type == LTT_STRING || td->type == LTT_SEQUENCE)
579 // fld->field_size = 0;
580 // else fld->field_size = -1;
582 if(td
->type
== LTT_ENUM
){
583 fld
->field_type
->element_number
= td
->labels
.position
;
584 fld
->field_type
->enum_strings
= g_new(GQuark
,td
->labels
.position
);
585 for(i
=0;i
<td
->labels
.position
;i
++){
586 fld
->field_type
->enum_strings
[i
]
587 = g_quark_from_string(((char*)(td
->labels
.array
[i
])));
589 }else if(td
->type
== LTT_ARRAY
|| td
->type
== LTT_SEQUENCE
){
590 if(td
->type
== LTT_ARRAY
)
591 fld
->field_type
->element_number
= (unsigned)td
->size
;
592 fld
->field_type
->element_type
= g_new(LttType
*,1);
593 tmpTd
= td
->nested_type
;
594 fld
->field_type
->element_type
[0] = lookup_named_type(fac
, tmpTd
);
595 fld
->child
= g_new(LttField
*, 1);
596 fld
->child
[0] = g_new(LttField
, 1);
598 // fld->child[0]->field_pos = 0;
599 fld
->child
[0]->field_type
= fld
->field_type
->element_type
[0];
600 fld
->child
[0]->offset_root
= fld
->offset_root
;
601 fld
->child
[0]->fixed_root
= fld
->fixed_root
;
602 fld
->child
[0]->offset_parent
= 0;
603 fld
->child
[0]->fixed_parent
= 1;
604 // fld->child[0]->base_address = NULL;
605 fld
->child
[0]->field_size
= 0;
606 fld
->child
[0]->field_fixed
= -1;
607 fld
->child
[0]->parent
= fld
;
608 fld
->child
[0]->child
= NULL
;
609 fld
->child
[0]->current_element
= 0;
610 construct_types_and_fields(fac
, tmpTd
, fld
->child
[0]);
611 }else if(td
->type
== LTT_STRUCT
){
612 fld
->field_type
->element_number
= td
->fields
.position
;
614 if(fld
->field_type
->element_type
== NULL
){
615 fld
->field_type
->element_type
= g_new(LttType
*, td
->fields
.position
);
621 fld
->child
= g_new(LttField
*, td
->fields
.position
);
622 for(i
=0;i
<td
->fields
.position
;i
++){
623 tmpTd
= ((type_fields
*)(td
->fields
.array
[i
]))->type
;
626 fld
->field_type
->element_type
[i
] = lookup_named_type(fac
, tmpTd
);
627 fld
->child
[i
] = g_new(LttField
,1);
629 fld
->child
[i
]->field_pos
= i
;
630 fld
->child
[i
]->field_type
= fld
->field_type
->element_type
[i
];
633 fld
->child
[i
]->field_type
->element_name
634 = g_quark_from_string(((type_fields
*)(td
->fields
.array
[i
]))->name
);
637 fld
->child
[i
]->offset_root
= -1;
638 fld
->child
[i
]->fixed_root
= -1;
639 fld
->child
[i
]->offset_parent
= -1;
640 fld
->child
[i
]->fixed_parent
= -1;
641 // fld->child[i]->base_address = NULL;
642 fld
->child
[i
]->field_size
= 0;
643 fld
->child
[i
]->field_fixed
= -1;
644 fld
->child
[i
]->parent
= fld
;
645 fld
->child
[i
]->child
= NULL
;
646 fld
->child
[i
]->current_element
= 0;
647 construct_types_and_fields(fac
, tmpTd
, fld
->child
[i
]);
654 /*****************************************************************************
656 * lookup_named_type: search named type in the table
659 * fac : facility struct
662 * : either find the named type, or create a new LttType
663 ****************************************************************************/
665 LttType
* lookup_named_type(LttFacility
*fac
, GQuark type_name
)
667 LttType
*type
= NULL
;
670 type
= g_datalist_id_get_data(&fac
->named_types
, name
);
672 g_assert(type
!= NULL
);
675 /* Create the type */
676 type
= g_new(LttType
,1);
677 type
->type_name
= name
;
678 type
->type_class
= td
->type
;
679 if(td
->fmt
) type
->fmt
= g_strdup(td
->fmt
);
680 else type
->fmt
= NULL
;
681 type
->size
= td
->size
;
682 type
->enum_strings
= NULL
;
683 type
->element_type
= NULL
;
684 type
->element_number
= 0;
686 if(td
->type_name
!= NULL
)
687 g_datalist_id_set_data_full(&fac
->named_types
, name
,
688 type
, (GDestroyNotify
)freeLttNamedType
);
695 /*****************************************************************************
697 * ltt_facility_close : close a facility, decrease its usage count,
698 * if usage count = 0, release the memory
700 * f : facility that will be closed
701 ****************************************************************************/
703 void ltt_facility_close(LttFacility
*f
)
705 //release the memory it occupied
709 /*****************************************************************************
710 * Functions to release the memory occupied by the facility
711 ****************************************************************************/
713 void freeFacility(LttFacility
* fac
)
718 for(i
=0; i
<fac
->events
->len
; i
++) {
719 et
= &g_array_index (fac
->events
, LttEventType
, i
);
722 g_array_free(fac
->events
, TRUE
);
724 g_datalist_clear(&fac
->events_by_name
);
726 // g_datalist_clear(&fac->named_types);
729 void freeEventtype(LttEventType
* evType
)
733 if(evType
->description
)
734 g_free(evType
->description
);
736 for(i
=0; i
<evType
->fields
->len
;i
++) {
737 freeLttType(&g_array_index(evType
->fields
, LttType
, i
));
739 g_array_free(evType
->fields
, TRUE
);
740 g_datalist_clear(&evType
->fields_by_name
);
743 void freeLttType(LttType
* type
)
751 g_datalist_clear(&type
->enum_map
);
754 for(i
=0; i
<type
->fields
->len
; i
++) {
755 freeLttField(&g_array_index(type
->fields
, LttField
, i
));
757 g_array_free(type
->fields
, TRUE
);
759 if(type
->fields_by_name
)
760 g_datalist_clear(&type
->fields_by_name
);
763 void freeLttNamedType(LttType
* type
)
768 void copy_enum_element(GQuark keyid
, gpointer data
, gpointer user_data
)
770 int *value
= gpointer data
;
774 void freeLttField(LttField
* field
)
776 if(field
->description
)
777 g_free(field
->description
);
778 if(field
->dynamic_offsets
)
779 g_array_free(field
->dynamic_offsets
, TRUE
);
780 freeLttType(field
->type
);
783 /*****************************************************************************
785 * ltt_facility_name : obtain the facility's name
789 * GQuark : the facility's name
790 ****************************************************************************/
792 GQuark
ltt_facility_name(LttFacility
*f
)
797 /*****************************************************************************
799 * ltt_facility_checksum : obtain the facility's checksum
803 * : the checksum of the facility
804 ****************************************************************************/
806 guint32
ltt_facility_checksum(LttFacility
*f
)
811 /*****************************************************************************
813 * ltt_facility_base_id : obtain the facility base id
817 * : the base id of the facility
818 ****************************************************************************/
820 guint
ltt_facility_id(LttFacility
*f
)
825 /*****************************************************************************
827 * ltt_facility_eventtype_number: obtain the number of the event types
829 * f : the facility that will be closed
831 * : the number of the event types
832 ****************************************************************************/
834 guint8
ltt_facility_eventtype_number(LttFacility
*f
)
836 return (f
->events
->len
);
839 /*****************************************************************************
841 * ltt_facility_eventtype_get: obtain the event type according to event id
842 * from 0 to event_number - 1
844 * f : the facility that will be closed
846 * LttEventType * : the event type required
847 ****************************************************************************/
849 LttEventType
*ltt_facility_eventtype_get(LttFacility
*f
, guint8 i
)
851 if(!f
->exists
) return NULL
;
853 g_assert(i
< f
->events
->len
);
854 return &g_array_index(f
->events
, LttEventType
, i
);
857 /*****************************************************************************
859 * ltt_facility_eventtype_get_by_name
860 * : obtain the event type according to event name
861 * event name is unique in the facility
864 * name : the name of the event
866 * LttEventType * : the event type required
867 ****************************************************************************/
869 LttEventType
*ltt_facility_eventtype_get_by_name(LttFacility
*f
, GQuark name
)
871 LttEventType
*et
= g_datalist_id_get_data(&f
->events_by_name
, name
);
This page took 0.050719 seconds and 4 git commands to generate.