[sysprof] libsysprof: add summary information for memprof profile
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [sysprof] libsysprof: add summary information for memprof profile
- Date: Wed, 19 Feb 2020 08:17:19 +0000 (UTC)
commit 8e81b1fcf94622276f2bb015ba79719193e36f39
Author: Christian Hergert <chergert redhat com>
Date: Wed Feb 19 00:16:36 2020 -0800
libsysprof: add summary information for memprof profile
src/libsysprof-ui/sysprof-memprof-page.c | 163 ++++++++++++++++++++++++++----
src/libsysprof-ui/sysprof-memprof-page.ui | 160 ++++++++++++++++++++++++++++-
src/libsysprof/sysprof-memprof-profile.c | 143 ++++++++++++++++++++++++++
src/libsysprof/sysprof-memprof-profile.h | 21 ++++
4 files changed, 464 insertions(+), 23 deletions(-)
---
diff --git a/src/libsysprof-ui/sysprof-memprof-page.c b/src/libsysprof-ui/sysprof-memprof-page.c
index 4f61cb7..1d5c72e 100644
--- a/src/libsysprof-ui/sysprof-memprof-page.c
+++ b/src/libsysprof-ui/sysprof-memprof-page.c
@@ -62,6 +62,11 @@ typedef struct
GtkRadioButton *summary;
GtkRadioButton *all_allocs;
GtkRadioButton *temp_allocs;
+ GtkLabel *temp_allocs_count;
+ GtkLabel *num_allocs;
+ GtkLabel *leaked_allocs;
+ GtkLabel *peak_allocs;
+ GtkListBox *by_size;
GCancellable *cancellable;
@@ -160,6 +165,117 @@ build_functions_store (StackNode *node,
}
+static void
+update_summary (SysprofMemprofPage *self,
+ SysprofMemprofProfile *profile)
+{
+ SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self);
+ SysprofMemprofStats stats;
+ g_autoptr(GString) str = NULL;
+
+ g_assert (SYSPROF_IS_MEMPROF_PAGE (self));
+ g_assert (SYSPROF_IS_MEMPROF_PROFILE (profile));
+
+ sysprof_memprof_profile_get_stats (profile, &stats);
+
+ str = g_string_new (NULL);
+
+ g_string_append_printf (str, "%"G_GINT64_FORMAT, stats.n_allocs);
+ gtk_label_set_label (priv->num_allocs, str->str);
+ g_string_truncate (str, 0);
+
+ g_string_append_printf (str, "%"G_GINT64_FORMAT, stats.leaked_allocs);
+ gtk_label_set_label (priv->leaked_allocs, str->str);
+ g_string_truncate (str, 0);
+
+ g_string_append_printf (str, "%"G_GINT64_FORMAT, stats.temp_allocs);
+ gtk_label_set_label (priv->temp_allocs_count, str->str);
+ g_string_truncate (str, 0);
+
+ gtk_container_foreach (GTK_CONTAINER (priv->by_size),
+ (GtkCallback)gtk_widget_destroy,
+ NULL);
+
+ for (guint i = 0; i < G_N_ELEMENTS (stats.by_size); i++)
+ {
+ g_autofree gchar *prevstr = NULL;
+ g_autofree gchar *sizestr = NULL;
+ g_autofree gchar *title_str = NULL;
+ g_autofree gchar *subtitle_str = NULL;
+ g_autofree gchar *allocstr = NULL;
+ g_autofree gchar *tempstr = NULL;
+ g_autofree gchar *leakedstr = NULL;
+ g_autofree gchar *allstr = NULL;
+ GtkWidget *row;
+ GtkWidget *title;
+ GtkWidget *subtitle;
+ GtkWidget *prog;
+ GtkWidget *box;
+
+ if (stats.by_size[i].n_allocs == 0)
+ continue;
+
+ row = gtk_list_box_row_new ();
+ title = gtk_label_new (NULL);
+ subtitle = gtk_label_new (NULL);
+ prog = gtk_level_bar_new_for_interval (0, stats.n_allocs);
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
+
+ sizestr = g_format_size_full (stats.by_size[i].bucket, G_FORMAT_SIZE_IEC_UNITS);
+ if (i == 0)
+ {
+ title_str = g_strdup_printf ("≤ %s", sizestr);
+ }
+ else
+ {
+ /* translators: %s is replaced with a memory size such as "32 bytes" */
+ prevstr = g_format_size_full (stats.by_size[i-1].bucket, G_FORMAT_SIZE_IEC_UNITS);
+ /* translators: %s is replaced with the the lower and upper bound memory sizes in bytes */
+ title_str = g_strdup_printf (_("> %s to %s"), prevstr, sizestr);
+ }
+
+ gtk_label_set_label (GTK_LABEL (title), title_str);
+ gtk_label_set_xalign (GTK_LABEL (title), 0);
+ dzl_gtk_widget_add_style_class (title, "dim-label");
+
+ gtk_widget_set_margin_start (box, 6);
+ gtk_widget_set_margin_end (box, 6);
+
+ gtk_widget_set_margin_top (prog, 1);
+ gtk_widget_set_margin_bottom (prog, 1);
+
+ allocstr = g_strdup_printf ("%"G_GINT64_FORMAT, stats.by_size[i].n_allocs);
+ tempstr = g_strdup_printf ("%"G_GINT64_FORMAT, stats.by_size[i].temp_allocs);
+ allstr = g_format_size_full (stats.by_size[i].allocated,
+ G_FORMAT_SIZE_IEC_UNITS);
+ subtitle_str = g_strdup_printf ("%s allocations, %s temporary, %s",
+ allocstr, tempstr, allstr);
+
+ gtk_label_set_label (GTK_LABEL (subtitle), subtitle_str);
+ gtk_label_set_xalign (GTK_LABEL (subtitle), 0);
+
+#if 0
+ /* TODO: Make this chunked by [temp][rest]... */
+ gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (prog),
+ GTK_LEVEL_BAR_OFFSET_HIGH,
+ stats.by_size[i].temp_allocs);
+ gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (prog),
+ GTK_LEVEL_BAR_OFFSET_LOW,
+ stats.by_size[i].n_allocs);
+#endif
+ gtk_level_bar_set_value (GTK_LEVEL_BAR (prog),
+ stats.by_size[i].n_allocs);
+
+ gtk_container_add (GTK_CONTAINER (row), box);
+ gtk_container_add (GTK_CONTAINER (box), title);
+ gtk_container_add (GTK_CONTAINER (box), prog);
+ gtk_container_add (GTK_CONTAINER (box), subtitle);
+ gtk_container_add (GTK_CONTAINER (priv->by_size), row);
+
+ gtk_widget_show_all (row);
+ }
+}
+
static void
sysprof_memprof_page_load (SysprofMemprofPage *self,
SysprofMemprofProfile *profile)
@@ -189,8 +305,13 @@ sysprof_memprof_page_load (SysprofMemprofPage *self,
if (!g_set_object (&priv->profile, profile))
return;
+ update_summary (self, profile);
+
if (sysprof_memprof_profile_is_empty (profile))
- return;
+ {
+ gtk_stack_set_visible_child_name (priv->stack, "summary");
+ return;
+ }
stash = sysprof_memprof_profile_get_stash (profile);
@@ -802,7 +923,6 @@ sysprof_memprof_page_generate_cb (GObject *object,
{
SysprofProfile *profile = (SysprofProfile *)object;
SysprofMemprofPage *self;
- SysprofMemprofPagePrivate *priv;
g_autoptr(GTask) task = user_data;
g_autoptr(GError) error = NULL;
@@ -811,15 +931,11 @@ sysprof_memprof_page_generate_cb (GObject *object,
g_assert (G_IS_TASK (task));
self = g_task_get_source_object (task);
- priv = sysprof_memprof_page_get_instance_private (self);
if (!sysprof_profile_generate_finish (profile, result, &error))
g_task_return_error (task, g_error_copy (error));
else
sysprof_memprof_page_set_profile (self, SYSPROF_MEMPROF_PROFILE (profile));
-
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- gtk_stack_set_visible_child_name (priv->stack, "callgraph");
}
static void
@@ -876,19 +992,6 @@ sysprof_memprof_page_load_finish (SysprofPage *page,
return g_task_propagate_boolean (G_TASK (result), error);
}
-static void
-do_summary (SysprofMemprofPage *self)
-{
-#if 0
- SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self);
-
- g_assert (SYSPROF_IS_MEMPROF_PAGE (self));
-
- g_cancellable_cancel (priv->cancellable);
- gtk_stack_set_visible_child_name (priv->stack, "summary");
-#endif
-}
-
static void
do_allocs (SysprofMemprofPage *self,
SysprofMemprofMode mode)
@@ -914,7 +1017,7 @@ mode_notify_active (SysprofMemprofPage *self,
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
{
if (button == priv->summary)
- do_summary (self);
+ do_allocs (self, SYSPROF_MEMPROF_MODE_SUMMARY);
else if (button == priv->all_allocs)
do_allocs (self, SYSPROF_MEMPROF_MODE_ALL_ALLOCS);
else if (button == priv->temp_allocs)
@@ -922,6 +1025,19 @@ mode_notify_active (SysprofMemprofPage *self,
}
}
+static void
+sep_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ if (before != NULL)
+ gtk_list_box_row_set_header (row,
+ g_object_new (GTK_TYPE_SEPARATOR,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL));
+}
+
static void
sysprof_memprof_page_finalize (GObject *object)
{
@@ -1010,6 +1126,7 @@ sysprof_memprof_page_class_init (SysprofMemprofPageClass *klass)
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/sysprof/ui/sysprof-memprof-page.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, by_size);
gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, callers_view);
gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, function_size_cell);
gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, function_size_column);
@@ -1020,6 +1137,10 @@ sysprof_memprof_page_class_init (SysprofMemprofPageClass *klass)
gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, all_allocs);
gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, temp_allocs);
gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, summary);
+ gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, temp_allocs_count);
+ gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, num_allocs);
+ gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, leaked_allocs);
+ gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, peak_allocs);
bindings = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (bindings, GDK_KEY_Left, GDK_MOD1_MASK, "go-previous", 0);
@@ -1042,6 +1163,8 @@ sysprof_memprof_page_init (SysprofMemprofPage *self)
gtk_stack_set_visible_child_name (priv->stack, "empty-state");
+ gtk_list_box_set_header_func (priv->by_size, sep_header_func, NULL, NULL);
+
g_signal_connect_object (priv->all_allocs,
"notify::active",
G_CALLBACK (mode_notify_active),
diff --git a/src/libsysprof-ui/sysprof-memprof-page.ui b/src/libsysprof-ui/sysprof-memprof-page.ui
index b602a92..c123ab3 100644
--- a/src/libsysprof-ui/sysprof-memprof-page.ui
+++ b/src/libsysprof-ui/sysprof-memprof-page.ui
@@ -22,7 +22,7 @@
<object class="GtkRadioButton" id="summary">
<property name="label" translatable="yes">Summary</property>
<property name="draw-indicator">false</property>
- <property name="visible">false</property>
+ <property name="visible">true</property>
<property name="active">false</property>
</object>
</child>
@@ -57,6 +57,160 @@
<child>
<object class="GtkStack" id="stack">
<property name="visible">true</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin">16</property>
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="DzlThreeGrid">
+ <property name="expand">true</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">18</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Peak Allocation</property>
+ <property name="halign">end</property>
+ <property name="visible">false</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="column">left</property>
+ <property name="row">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="peak_allocs">
+ <property name="xalign">0</property>
+ <property name="selectable">true</property>
+ <property name="visible">false</property>
+ </object>
+ <packing>
+ <property name="column">center</property>
+ <property name="row">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Number of Allocations</property>
+ <property name="halign">end</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="column">left</property>
+ <property name="row">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="num_allocs">
+ <property name="xalign">0</property>
+ <property name="selectable">true</property>
+ <property name="visible">true</property>
+ </object>
+ <packing>
+ <property name="column">center</property>
+ <property name="row">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Leaked Allocations</property>
+ <property name="halign">end</property>
+ <property name="visible">false</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="column">left</property>
+ <property name="row">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="leaked_allocs">
+ <property name="xalign">0</property>
+ <property name="selectable">true</property>
+ <property name="visible">false</property>
+ </object>
+ <packing>
+ <property name="column">center</property>
+ <property name="row">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Temporary Allocations</property>
+ <property name="halign">end</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="column">left</property>
+ <property name="row">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="temp_allocs_count">
+ <property name="xalign">0</property>
+ <property name="selectable">true</property>
+ <property name="width-chars">50</property>
+ <property name="visible">true</property>
+ </object>
+ <packing>
+ <property name="column">center</property>
+ <property name="row">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Allocations by Size</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="column">left</property>
+ <property name="row">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox" id="by_size">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="column">center</property>
+ <property name="row">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">summary</property>
+ </packing>
+ </child>
<child>
<object class="GtkPaned">
<property name="orientation">horizontal</property>
@@ -265,8 +419,8 @@
<child>
<object class="DzlEmptyState">
<property name="icon-name">content-loading-symbolic</property>
- <property name="title" translatable="yes">Generating Callgraph</property>
- <property name="subtitle" translatable="yes">Sysprof is busy creating the selected
callgraph.</property>
+ <property name="title" translatable="yes">Analyzing Memory Allocations</property>
+ <property name="subtitle" translatable="yes">Sysprof is busy analyzing memory
allocations.</property>
<property name="visible">true</property>
</object>
<packing>
diff --git a/src/libsysprof/sysprof-memprof-profile.c b/src/libsysprof/sysprof-memprof-profile.c
index cbe87fd..e338906 100644
--- a/src/libsysprof/sysprof-memprof-profile.c
+++ b/src/libsysprof/sysprof-memprof-profile.c
@@ -56,6 +56,7 @@ typedef struct
rax *rax;
GArray *resolved;
SysprofMemprofMode mode;
+ SysprofMemprofStats stats;
} Generate;
struct _SysprofMemprofProfile
@@ -397,6 +398,131 @@ compare_alloc (gconstpointer a,
return 0;
}
+static guint
+get_bucket (gint64 size)
+{
+ if (size <= 32)
+ return 0;
+ if (size <= 64)
+ return 1;
+ if (size <= 128)
+ return 2;
+ if (size <= 256)
+ return 3;
+ if (size <= 512)
+ return 4;
+ if (size <= 1024)
+ return 5;
+ if (size <= 4096)
+ return 6;
+ if (size <= 4096*4)
+ return 7;
+ if (size <= 4096*8)
+ return 8;
+ if (size <= 4096*16)
+ return 9;
+ if (size <= 4096*32)
+ return 10;
+ if (size <= 4096*64)
+ return 11;
+ if (size <= 4096*256)
+ return 12;
+ return 13;
+}
+
+static void
+summary_worker (Generate *g)
+{
+ g_autoptr(GArray) allocs = NULL;
+ SysprofCaptureFrameType type;
+ SysprofCaptureAddress last_addr = 0;
+ guint last_bucket = 0;
+
+ g_assert (g != NULL);
+ g_assert (g->reader != NULL);
+
+ allocs = g_array_new (FALSE, FALSE, sizeof (Alloc));
+
+ sysprof_capture_reader_reset (g->reader);
+
+ g->stats.by_size[0].bucket = 32;
+ g->stats.by_size[1].bucket = 64;
+ g->stats.by_size[2].bucket = 128;
+ g->stats.by_size[3].bucket = 256;
+ g->stats.by_size[4].bucket = 512;
+ g->stats.by_size[5].bucket = 1024;
+ g->stats.by_size[6].bucket = 4096;
+ g->stats.by_size[7].bucket = 4096*4;
+ g->stats.by_size[8].bucket = 4096*8;
+ g->stats.by_size[9].bucket = 4096*16;
+ g->stats.by_size[10].bucket = 4096*32;
+ g->stats.by_size[11].bucket = 4096*64;
+ g->stats.by_size[12].bucket = 4096*256;
+ g->stats.by_size[13].bucket = 4096*256;
+
+ while (sysprof_capture_reader_peek_type (g->reader, &type))
+ {
+ if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
+ {
+ const SysprofCaptureAllocation *ev;
+ Alloc a;
+
+ if (!(ev = sysprof_capture_reader_read_allocation (g->reader)))
+ break;
+
+ a.pid = ev->frame.pid;
+ a.tid = ev->tid;
+ a.time = ev->frame.time;
+ a.addr = ev->alloc_addr;
+ a.size = ev->alloc_size;
+ a.frame_num = 0;
+
+ g_array_append_val (allocs, a);
+
+ if (a.size > 0)
+ g->stats.n_allocs++;
+ }
+ else
+ {
+ if (!sysprof_capture_reader_skip (g->reader))
+ break;
+ }
+ }
+
+ g_array_sort (allocs, compare_alloc);
+
+ for (guint i = 0; i < allocs->len; i++)
+ {
+ const Alloc *a = &g_array_index (allocs, Alloc, i);
+
+ if (a->size <= 0)
+ {
+ if (last_addr == a->addr)
+ {
+ g->stats.temp_allocs++;
+ g->stats.by_size[last_bucket].temp_allocs++;
+ }
+
+ g->stats.leaked_allocs--;
+
+ last_addr = 0;
+ last_bucket = 0;
+ }
+ else
+ {
+ guint b = get_bucket (a->size);
+
+ g->stats.n_allocs++;
+ g->stats.leaked_allocs++;
+ g->stats.by_size[b].n_allocs++;
+ g->stats.by_size[b].allocated += a->size;
+
+ last_addr = a->addr;
+ last_bucket = b;
+ }
+ }
+}
+
static void
temp_allocs_worker (Generate *g)
{
@@ -639,6 +765,10 @@ sysprof_memprof_profile_generate_worker (GTask *task,
{
temp_allocs_worker (g);
}
+ else if (g->mode == SYSPROF_MEMPROF_MODE_SUMMARY)
+ {
+ summary_worker (g);
+ }
/* Release some data we don't need anymore */
g_clear_pointer (&g->resolved, g_array_unref);
@@ -781,3 +911,16 @@ sysprof_memprof_profile_new_with_selection (SysprofSelection *selection)
"selection", selection,
NULL);
}
+
+void
+sysprof_memprof_profile_get_stats (SysprofMemprofProfile *self,
+ SysprofMemprofStats *stats)
+{
+ g_return_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self));
+ g_return_if_fail (stats != NULL);
+
+ if (self->g != NULL)
+ *stats = self->g->stats;
+ else
+ memset (stats, 0, sizeof *stats);
+}
diff --git a/src/libsysprof/sysprof-memprof-profile.h b/src/libsysprof/sysprof-memprof-profile.h
index c1f45c2..1a5badd 100644
--- a/src/libsysprof/sysprof-memprof-profile.h
+++ b/src/libsysprof/sysprof-memprof-profile.h
@@ -35,10 +35,28 @@ G_BEGIN_DECLS
typedef enum
{
+ SYSPROF_MEMPROF_MODE_SUMMARY = 0,
SYSPROF_MEMPROF_MODE_ALL_ALLOCS = 1,
SYSPROF_MEMPROF_MODE_TEMP_ALLOCS = 2,
} SysprofMemprofMode;
+typedef struct
+{
+ gint64 n_allocs;
+ gint64 leaked_allocs;
+ gint64 leaked_allocs_size;
+ gint64 temp_allocs;
+ gint64 temp_allocs_size;
+ struct {
+ gint64 bucket;
+ gint64 n_allocs;
+ gint64 temp_allocs;
+ gint64 allocated;
+ } by_size[14];
+ /*< private >*/
+ gint64 _reserved[32];
+} SysprofMemprofStats;
+
SYSPROF_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (SysprofMemprofProfile, sysprof_memprof_profile, SYSPROF, MEMPROF_PROFILE, GObject)
@@ -47,6 +65,9 @@ SysprofProfile *sysprof_memprof_profile_new (void);
SYSPROF_AVAILABLE_IN_3_36
SysprofProfile *sysprof_memprof_profile_new_with_selection (SysprofSelection *selection);
SYSPROF_AVAILABLE_IN_3_36
+void sysprof_memprof_profile_get_stats (SysprofMemprofProfile *self,
+ SysprofMemprofStats *stats);
+SYSPROF_AVAILABLE_IN_3_36
SysprofMemprofMode sysprof_memprof_profile_get_mode (SysprofMemprofProfile *self);
SYSPROF_AVAILABLE_IN_3_36
void sysprof_memprof_profile_set_mode (SysprofMemprofProfile *self,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]