stats: add a tracer that collects pipeline statistics
authorStefan Sauer <ensonic@users.sf.net>
Mon, 28 Oct 2013 07:08:20 +0000 (08:08 +0100)
committerStefan Sauer <ensonic@users.sf.net>
Mon, 5 Oct 2015 18:59:39 +0000 (20:59 +0200)
This is more or less equiv to the the statistics in gst-tracelib.

plugins/tracers/Makefile.am
plugins/tracers/gststats.c [new file with mode: 0644]
plugins/tracers/gststats.h [new file with mode: 0644]
plugins/tracers/gsttracers.c

index ede77f6..498cb7b 100644 (file)
@@ -4,6 +4,7 @@ plugin_LTLIBRARIES = libgstcoretracers.la
 libgstcoretracers_la_DEPENDENCIES = $(top_builddir)/gst/libgstreamer-@GST_API_VERSION@.la
 libgstcoretracers_la_SOURCES = \
   gstlog.c \
+  gststats.c \
        gsttracers.c
 
 libgstcoretracers_la_CFLAGS = $(GST_OBJ_CFLAGS)
@@ -14,7 +15,8 @@ libgstcoretracers_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 libgstcoretracers_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
 
 noinst_HEADERS = \
-  gstlog.h
+  gstlog.h \
+  gststats.h
 
 CLEANFILES = *.gcno *.gcda *.gcov *.gcov.out
 
diff --git a/plugins/tracers/gststats.c b/plugins/tracers/gststats.c
new file mode 100644 (file)
index 0000000..b610d26
--- /dev/null
@@ -0,0 +1,542 @@
+/* GStreamer
+ * Copyright (C) 2013 Stefan Sauer <ensonic@users.sf.net>
+ *
+ * gststats.c: tracing module that logs events
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "gststats.h"
+
+#include <stdio.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_stats_debug);
+#define GST_CAT_DEFAULT gst_stats_debug
+
+/* TODO(ensonic): the quark table from gst/gstquark.{c,h} is not exported
+ * - we need a tracer specific quark table
+ * - or we add a GQuark gst_quark_get (GstQuarkId id); there for external use
+ */
+enum _FuncEnum
+{
+  PUSH_BUFFER_PRE,
+  PUSH_BUFFER_POST,
+  N_FUNCS
+};
+
+static GQuark data_quark;
+static GQuark funcs[N_FUNCS];
+G_LOCK_DEFINE (_stats);
+
+#define _do_init \
+    GST_DEBUG_CATEGORY_INIT (gst_stats_debug, "stats", 0, "stats tracer"); \
+    data_quark = g_quark_from_static_string ("gststats:data"); \
+    funcs[PUSH_BUFFER_PRE] = g_quark_from_static_string ("push_buffer::pre"); \
+    funcs[PUSH_BUFFER_POST] = g_quark_from_static_string ("push_buffer::post");
+#define gst_stats_tracer_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstStatsTracer, gst_stats_tracer, GST_TYPE_TRACER,
+    _do_init);
+
+typedef struct
+{
+  /* human readable pad name and details */
+  gchar *name;
+  guint index;
+  GType type;
+  GstPadDirection dir;
+  /* buffer statistics */
+  guint num_buffers;
+  guint num_discont, num_gap, num_delta;
+  guint min_size, max_size, avg_size;
+  /* first and last activity on the pad, expected next_ts */
+  GstClockTime first_ts, last_ts, next_ts;
+  /* in which thread does it operate */
+  gpointer thread_id;
+} GstPadStats;
+
+typedef struct
+{
+  /* human readable element name */
+  gchar *name;
+  guint index;
+  GType type;
+  /* buffer statistics */
+  guint recv_buffers, sent_buffers;
+  guint64 recv_bytes, sent_bytes;
+  /* event, message statistics */
+  guint num_events, num_messages, num_queries;
+  /* first activity on the element */
+  GstClockTime first_ts, last_ts;
+  /* time spend in this element */
+  GstClockTime treal;
+  /* hierarchy */
+  guint parent_ix;
+} GstElementStats;
+
+/* data helper */
+
+static GstElementStats *
+fill_element_stats (GstStatsTracer * self, GstElement * element)
+{
+  GstElementStats *stats = g_slice_new0 (GstElementStats);
+
+  if (GST_IS_BIN (element)) {
+    self->num_bins++;
+  }
+  stats->index = self->num_elements++;
+  stats->type = G_OBJECT_TYPE (element);
+  stats->first_ts = GST_CLOCK_TIME_NONE;
+  stats->parent_ix = G_MAXUINT;
+  return stats;
+}
+
+#if 0
+static GstElementStats *
+get_element_stats_by_id (GstStatsTracer * self, guint ix)
+{
+  return g_ptr_array_index (self->elements, ix);
+}
+#endif
+
+static inline GstElementStats *
+get_element_stats (GstStatsTracer * self, GstElement * element)
+{
+  GstElementStats *stats;
+
+  G_LOCK (_stats);
+  if (!(stats = g_object_get_qdata ((GObject *) element, data_quark))) {
+    stats = fill_element_stats (self, element);
+    g_object_set_qdata ((GObject *) element, data_quark, stats);
+    if (self->elements->len <= stats->index)
+      g_ptr_array_set_size (self->elements, stats->index + 1);
+    g_ptr_array_index (self->elements, stats->index) = stats;
+  }
+  G_UNLOCK (_stats);
+  if (G_UNLIKELY (stats->parent_ix == G_MAXUINT)) {
+    GstElement *parent = GST_ELEMENT_PARENT (element);
+    if (parent) {
+      GstElementStats *parent_stats = get_element_stats (self, parent);
+      stats->parent_ix = parent_stats->index;
+    }
+  }
+  if (G_UNLIKELY (!stats->name)) {
+    if (GST_OBJECT_NAME (element)) {
+      stats->name = g_strdup (GST_OBJECT_NAME (element));
+    }
+  }
+  return stats;
+}
+
+static void
+free_element_stats (gpointer data)
+{
+  g_slice_free (GstElementStats, data);
+}
+
+static GstPadStats *
+fill_pad_stats (GstStatsTracer * self, GstPad * pad)
+{
+  GstPadStats *stats = g_slice_new0 (GstPadStats);
+
+  if (GST_IS_GHOST_PAD (pad)) {
+    self->num_ghostpads++;
+  }
+  stats->index = self->num_pads++;
+  stats->type = G_OBJECT_TYPE (pad);
+  stats->dir = GST_PAD_DIRECTION (pad);
+  stats->min_size = G_MAXUINT;
+  stats->first_ts = stats->last_ts = stats->next_ts = GST_CLOCK_TIME_NONE;
+  stats->thread_id = g_thread_self ();
+  return stats;
+}
+
+static GstPadStats *
+get_pad_stats (GstStatsTracer * self, GstPad * pad)
+{
+  GstPadStats *stats;
+
+  if (!pad)
+    return NULL;
+
+  G_LOCK (_stats);
+  if (!(stats = g_object_get_qdata ((GObject *) pad, data_quark))) {
+    stats = fill_pad_stats (self, pad);
+    g_object_set_qdata ((GObject *) pad, data_quark, stats);
+    if (self->pads->len <= stats->index)
+      g_ptr_array_set_size (self->pads, stats->index + 1);
+    g_ptr_array_index (self->pads, stats->index) = stats;
+  }
+  G_UNLOCK (_stats);
+  if (G_UNLIKELY (!stats->name)) {
+    GstObject *parent = GST_OBJECT_PARENT (pad);
+    /* yes we leak the names right now ... */
+    if (GST_IS_ELEMENT (parent)) {
+      /* pad is regular pad */
+      get_element_stats (self, GST_ELEMENT_CAST (parent));
+      if (GST_OBJECT_NAME (parent) && GST_OBJECT_NAME (pad)) {
+        stats->name =
+            g_strdup_printf ("%s_%s", GST_OBJECT_NAME (parent),
+            GST_OBJECT_NAME (pad));
+      }
+    } else if (GST_IS_GHOST_PAD (parent)) {
+      /* pad is proxy pad */
+      get_pad_stats (self, GST_PAD_CAST (parent));
+      if (GST_OBJECT_NAME (parent) && GST_OBJECT_NAME (pad)) {
+        stats->name =
+            g_strdup_printf ("%s_%s", GST_OBJECT_NAME (parent),
+            GST_OBJECT_NAME (pad));
+      }
+    }
+  }
+  return stats;
+}
+
+static void
+free_pad_stats (gpointer data)
+{
+  g_slice_free (GstPadStats, data);
+}
+
+static void
+do_pad_stats (GstStatsTracer * self, GstPad * pad, GstPadStats * stats,
+    GstBuffer * buffer, GstClockTime elapsed)
+{
+  guint size = gst_buffer_get_size (buffer);
+  gulong avg_size;
+
+  self->num_buffers++;
+  /* size stats */
+  avg_size = (((gulong) stats->avg_size * (gulong) stats->num_buffers) + size);
+  stats->num_buffers++;
+  stats->avg_size = (guint) (avg_size / stats->num_buffers);
+  if (size < stats->min_size)
+    stats->min_size = size;
+  else if (size > stats->max_size)
+    stats->max_size = size;
+  /* time stats */
+  if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (stats->first_ts)))
+    stats->first_ts = elapsed;
+  stats->last_ts = elapsed;
+  /* flag stats */
+  if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_GAP))
+    stats->num_gap++;
+  if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
+    stats->num_delta++;
+  if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT))
+    stats->num_discont++;
+  /* TODO(ensonic): there is a bunch of new flags in 1.0 */
+
+  /* update timestamps */
+  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) +
+      GST_BUFFER_DURATION_IS_VALID (buffer)) {
+    stats->next_ts =
+        GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
+  } else {
+    stats->next_ts = GST_CLOCK_TIME_NONE;
+  }
+}
+
+static void
+do_transmission_stats (GstStatsTracer * self, GstPad * pad, GstBuffer * buf,
+    GstClockTime elapsed)
+{
+  GstObject *parent = GST_OBJECT_PARENT (pad);
+  GstElement *this =
+      GST_ELEMENT_CAST (GST_IS_PAD (parent) ? GST_OBJECT_PARENT (parent) :
+      parent);
+  GstElementStats *this_stats = get_element_stats (self, this);
+  GstPad *peer_pad = GST_PAD_PEER (pad);
+  GstElementStats *peer_stats;
+  GSList *bin_i_stats = NULL, *bin_o_stats = NULL;
+  GstPadStats *peer_pad_stats;
+
+  if (!peer_pad)
+    return;
+
+  parent = GST_OBJECT_PARENT (peer_pad);
+#ifdef _ENABLE_BLACK_MAGIC_
+  /* walk the ghost pad chain downstream to get the real pad */
+  /* if parent of peer_pad is a ghost-pad, then peer_pad is a proxy_pad */
+  while (parent && GST_IS_GHOST_PAD (parent)) {
+    peer_pad = GST_PAD_CAST (parent);
+    /* if this is now the ghost pad, get the peer of this */
+    get_pad_stats (self, peer_pad);
+    if (parent = GST_OBJECT_PARENT (peer_pad)) {
+      peer_stats = get_element_stats (self, GST_ELEMENT_CAST (parent));
+      bin_o_stats = g_slist_prepend (bin_o_stats, peer_stats);
+    }
+    peer_pad = GST_PAD_PEER (GST_GHOST_PAD_CAST (peer_pad));
+    parent = peer_pad ? GST_OBJECT_PARENT (peer_pad) : NULL;
+  }
+  /* walk the ghost pad chain upstream to get the real pad */
+  /* if peer_pad is a ghost-pad, then parent is a bin adn it is the parent of
+   * a proxy_pad */
+  while (peer_pad && GST_IS_GHOST_PAD (peer_pad)) {
+    get_pad_stats (self, peer_pad);
+    peer_stats = get_element_stats (self, GST_ELEMENT_CAST (parent));
+    bin_i_stats = g_slist_prepend (bin_i_stats, peer_stats);
+    peer_pad = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (peer_pad));
+    parent = peer_pad ? GST_OBJECT_PARENT (peer_pad) : NULL;
+  }
+#else
+  if (parent && GST_IS_GHOST_PAD (parent)) {
+    peer_pad = GST_PAD_CAST (parent);
+    parent = GST_OBJECT_PARENT (peer_pad);
+  }
+#endif
+
+  if (!parent) {
+    fprintf (stderr,
+        "%" GST_TIME_FORMAT " transmission on unparented target pad%s_%s\n",
+        GST_TIME_ARGS (elapsed), GST_DEBUG_PAD_NAME (peer_pad));
+    return;
+  }
+  peer_stats = get_element_stats (self, GST_ELEMENT_CAST (parent));
+
+  peer_pad_stats = get_pad_stats (self, peer_pad);
+  do_pad_stats (self, peer_pad, peer_pad_stats, buf, elapsed);
+
+  if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC) {
+    /* push */
+    this_stats->sent_buffers++;
+    peer_stats->recv_buffers++;
+    this_stats->sent_bytes += gst_buffer_get_size (buf);
+    peer_stats->recv_bytes += gst_buffer_get_size (buf);
+    /* time stats */
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (this_stats->first_ts))) {
+      this_stats->first_ts = elapsed;
+      //printf("%" GST_TIME_FORMAT " %s pushes on %s\n",GST_TIME_ARGS(elapsed),this_stats->name,peer_stats->name);
+    }
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (peer_stats->first_ts))) {
+      peer_stats->first_ts = elapsed + 1;
+      //printf("%" GST_TIME_FORMAT " %s is beeing pushed from %s\n",GST_TIME_ARGS(elapsed),peer_stats->name,this_stats->name);
+    }
+#ifdef _ENABLE_BLACK_MAGIC_
+    for (node = bin_o_stats; node; node = g_slist_next (node)) {
+      peer_stats = node->data;
+      peer_stats->sent_buffers++;
+      peer_stats->sent_bytes += gst_buffer_get_size (buf);
+    }
+    for (node = bin_i_stats; node; node = g_slist_next (node)) {
+      peer_stats = node->data;
+      peer_stats->recv_buffers++;
+      peer_stats->recv_bytes += gst_buffer_get_size (buf);
+    }
+#endif
+  } else {
+    /* pull */
+    peer_stats->sent_buffers++;
+    this_stats->recv_buffers++;
+    peer_stats->sent_bytes += gst_buffer_get_size (buf);
+    this_stats->recv_bytes += gst_buffer_get_size (buf);
+    /* time stats */
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (this_stats->first_ts))) {
+      this_stats->first_ts = elapsed + 1;
+      //printf("%" GST_TIME_FORMAT " %s pulls from %s\n",GST_TIME_ARGS(elapsed),this_stats->name,peer_stats->name);
+    }
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (peer_stats->first_ts))) {
+      peer_stats->first_ts = elapsed;
+      //printf("%" GST_TIME_FORMAT " %s is beeing pulled from %s\n",GST_TIME_ARGS(elapsed),peer_stats->name,this_stats->name);
+    }
+#ifdef _ENABLE_BLACK_MAGIC_
+    for (node = bin_i_stats; node; node = g_slist_next (node)) {
+      peer_stats = node->data;
+      peer_stats->sent_buffers++;
+      peer_stats->sent_bytes += gst_buffer_get_size (buf);
+    }
+    for (node = bin_o_stats; node; node = g_slist_next (node)) {
+      peer_stats = node->data;
+      peer_stats->recv_buffers++;
+      peer_stats->recv_bytes += gst_buffer_get_size (buf);
+    }
+#endif
+  }
+  g_slist_free (bin_o_stats);
+  g_slist_free (bin_i_stats);
+}
+
+static void
+do_element_stats (GstStatsTracer * self, GstPad * pad, GstClockTime elapsed1,
+    GstClockTime elapsed2)
+{
+  GstClockTimeDiff elapsed = GST_CLOCK_DIFF (elapsed1, elapsed2);
+  GstObject *parent = GST_OBJECT_PARENT (pad);
+  GstElement *this =
+      GST_ELEMENT_CAST (GST_IS_PAD (parent) ? GST_OBJECT_PARENT (parent) :
+      parent);
+  GstElementStats *this_stats = get_element_stats (self, this);
+  GstPad *peer_pad = GST_PAD_PEER (pad);
+  GstElementStats *peer_stats;
+
+  if (!peer_pad)
+    return;
+
+  /* walk the ghost pad chain downstream to get the real pad */
+  /* if parent of peer_pad is a ghost-pad, then peer_pad is a proxy_pad */
+  parent = GST_OBJECT_PARENT (peer_pad);
+  if (parent && GST_IS_GHOST_PAD (parent)) {
+    peer_pad = GST_PAD_CAST (parent);
+    /* if this is now the ghost pad, get the peer of this */
+    get_pad_stats (self, peer_pad);
+    if ((parent = GST_OBJECT_PARENT (peer_pad))) {
+      get_element_stats (self, GST_ELEMENT_CAST (parent));
+    }
+    peer_pad = GST_PAD_PEER (GST_GHOST_PAD_CAST (peer_pad));
+    parent = peer_pad ? GST_OBJECT_PARENT (peer_pad) : NULL;
+  }
+  /* walk the ghost pad chain upstream to get the real pad */
+  /* if peer_pad is a ghost-pad, then parent is a bin and it is the parent of
+   * a proxy_pad */
+  while (peer_pad && GST_IS_GHOST_PAD (peer_pad)) {
+    get_pad_stats (self, peer_pad);
+    get_element_stats (self, GST_ELEMENT_CAST (parent));
+    peer_pad = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (peer_pad));
+    parent = peer_pad ? GST_OBJECT_PARENT (peer_pad) : NULL;
+  }
+
+  if (!parent) {
+    GstPadStats *pad_stats = get_pad_stats (self, pad);
+
+    printf ("%" GST_TIME_FORMAT
+        " transmission on unparented target pad %s -> %s_%s\n",
+        GST_TIME_ARGS (elapsed), pad_stats->name,
+        GST_DEBUG_PAD_NAME (peer_pad));
+    return;
+  }
+  peer_stats = get_element_stats (self, GST_ELEMENT_CAST (parent));
+
+  /* we'd like to gather time spend in each element, but this does not make too
+   * much sense yet
+   * pure push/pull-based:
+   *   - the time spend in the push/pull_range is accounted for the peer and
+   *     removed from the current element
+   *   - this works for chains
+   *   - drawback is sink elements that block to sync have a high time usage
+   *     - we could rerun the ests with sync=false
+   * both:
+   *   - a.g. demuxers both push and pull. thus we subtract time for the pull
+   *     and the push operations, but never add anything.
+   *   - can we start a counter after push/pull in such elements and add then
+   *     time to the element upon next pad activity?
+   */
+#if 1
+  /* this does not make sense for demuxers */
+  this_stats->treal -= elapsed;
+  peer_stats->treal += elapsed;
+#else
+  /* this creates several >100% figures */
+  this_stats->treal += GST_CLOCK_DIFF (this_stats->last_ts, elapsed2) - elapsed;
+  peer_stats->treal += elapsed;
+  this_stats->last_ts = elapsed2;
+  peer_stats->last_ts = elapsed2;
+#endif
+}
+
+/* tracer class */
+
+static void gst_stats_tracer_finalize (GObject * obj);
+static void gst_stats_tracer_invoke (GstTracer * obj, GstTracerHookId id,
+    GstStructure * s);
+
+static void
+gst_stats_tracer_class_init (GstStatsTracerClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstTracerClass *gst_tracer_class = GST_TRACER_CLASS (klass);
+
+  gobject_class->finalize = gst_stats_tracer_finalize;
+
+  gst_tracer_class->invoke = gst_stats_tracer_invoke;
+}
+
+static void
+gst_stats_tracer_init (GstStatsTracer * self)
+{
+  g_object_set (self, "mask",
+      GST_TRACER_HOOK_BUFFERS | GST_TRACER_HOOK_EVENTS |
+      GST_TRACER_HOOK_MESSAGES | GST_TRACER_HOOK_QUERIES, NULL);
+  self->elements = g_ptr_array_new_with_free_func (free_element_stats);
+  self->pads = g_ptr_array_new_with_free_func (free_pad_stats);
+}
+
+static void
+do_push_buffer_pre (GstStatsTracer * self, GstStructure * s)
+{
+  GstPad *pad;
+  GstBuffer *buffer;
+  GstPadStats *stats;
+  guint64 ts;
+
+  gst_structure_get (s,
+      "pad", GST_TYPE_PAD, &pad,
+      ".ts", G_TYPE_UINT64, &ts, "buffer", GST_TYPE_BUFFER, &buffer, NULL);
+  stats = get_pad_stats (self, pad);
+
+  do_pad_stats (self, pad, stats, buffer, ts);
+  do_transmission_stats (self, pad, buffer, ts);
+}
+
+static void
+do_push_buffer_post (GstStatsTracer * self, GstStructure * s)
+{
+  GstPad *pad;
+  GstPadStats *stats;
+  guint64 ts;
+
+  gst_structure_get (s,
+      "pad", GST_TYPE_PAD, &pad, ".ts", G_TYPE_UINT64, &ts, NULL);
+  stats = get_pad_stats (self, pad);
+
+  do_element_stats (self, pad, stats->last_ts, ts);
+}
+
+static void
+gst_stats_tracer_invoke (GstTracer * obj, GstTracerHookId id, GstStructure * s)
+{
+  GstStatsTracer *self = GST_STATS_TRACER_CAST (obj);
+  GQuark func = gst_structure_get_name_id (s);
+
+  if (func == funcs[PUSH_BUFFER_PRE])
+    do_push_buffer_pre (self, s);
+  else if (func == funcs[PUSH_BUFFER_POST])
+    do_push_buffer_post (self, s);
+}
+
+static void
+gst_stats_tracer_finalize (GObject * obj)
+{
+  GstStatsTracer *self = GST_STATS_TRACER_CAST (obj);
+
+  /* print overall stats */
+  puts ("\nOverall Statistics:");
+  printf ("Number of Elements: %u\n", self->num_elements - self->num_bins);
+  printf ("Number of Bins: %u\n", self->num_bins);
+  printf ("Number of Pads: %u\n", self->num_pads - self->num_ghostpads);
+  printf ("Number of GhostPads: %u\n", self->num_ghostpads);
+  printf ("Number of Buffers passed: %" G_GUINT64_FORMAT "\n",
+      self->num_buffers);
+  printf ("Number of Events sent: %" G_GUINT64_FORMAT "\n", self->num_events);
+  printf ("Number of Message sent: %" G_GUINT64_FORMAT "\n",
+      self->num_messages);
+  printf ("Number of Queries sent: %" G_GUINT64_FORMAT "\n", self->num_queries);
+  //printf("Time: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (self->last_ts));
+
+  G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
diff --git a/plugins/tracers/gststats.h b/plugins/tracers/gststats.h
new file mode 100644 (file)
index 0000000..5b8e611
--- /dev/null
@@ -0,0 +1,69 @@
+/* GStreamer
+ * Copyright (C) 2013 Stefan Sauer <ensonic@users.sf.net>
+ *
+ * gststats.h: tracing module that logs events
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_STATS_TRACER_H__
+#define __GST_STATS_TRACER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_STATS_TRACER \
+  (gst_stats_tracer_get_type())
+#define GST_STATS_TRACER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_STATS_TRACER,GstStatsTracer))
+#define GST_STATS_TRACER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_STATS_TRACER,GstStatsTracerClass))
+#define GST_IS_STATS_TRACER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_STATS_TRACER))
+#define GST_IS_STATS_TRACER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_STATS_TRACER))
+#define GST_STATS_TRACER_CAST(obj) ((GstStatsTracer *)(obj))
+
+typedef struct _GstStatsTracer GstStatsTracer;
+typedef struct _GstStatsTracerClass GstStatsTracerClass;
+
+/**
+ * GstStatsTracer:
+ *
+ * Opaque #GstStatsTracer data structure
+ */
+struct _GstStatsTracer {
+  GstTracer     parent;
+
+  /*< private >*/
+  GPtrArray *elements;
+  GPtrArray *pads;
+  guint64 num_buffers, num_events, num_messages, num_queries;
+  guint num_elements, num_bins, num_pads, num_ghostpads;
+};
+
+struct _GstStatsTracerClass {
+  GstTracerClass parent_class;
+
+  /* signals */
+};
+
+G_GNUC_INTERNAL GType gst_stats_tracer_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_STATS_TRACER_H__ */
index 1a0ed9d..bbe3a92 100644 (file)
 
 #include <gst/gst.h>
 #include "gstlog.h"
+#include "gststats.h"
 
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
   if (!gst_tracer_register (plugin, "log", gst_log_tracer_get_type ()))
     return FALSE;
+  if (!gst_tracer_register (plugin, "stats", gst_stats_tracer_get_type ()))
+    return FALSE;
   return TRUE;
 }