From 8d37b0ba55d6bbe3d3e5e8863e43b1e88e6bac96 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Thu, 6 Jul 2023 17:08:38 -0600 Subject: [PATCH 1/8] Add GstdStats object placeholder Add new GstdStats object placeholder to handle tracers stats collection. GstdStats has enable and stats properties --- gst_client/gst_client.c | 8 ++ libgstd/gstd_parser.c | 43 ++++++++++ libgstd/gstd_session.c | 25 +++++- libgstd/gstd_session.h | 6 ++ libgstd/gstd_stats.c | 186 ++++++++++++++++++++++++++++++++++++++++ libgstd/gstd_stats.h | 58 +++++++++++++ libgstd/meson.build | 1 + 7 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 libgstd/gstd_stats.c create mode 100644 libgstd/gstd_stats.h diff --git a/gst_client/gst_client.c b/gst_client/gst_client.c index 6bf9699f..1d0554d9 100644 --- a/gst_client/gst_client.c +++ b/gst_client/gst_client.c @@ -203,6 +203,14 @@ static GstdClientCmd cmds[] = { "Enable/Disable debug threshold reset", "debug_reset "}, + {"stats_enable", gstd_client_cmd_socket, + "Enables/Disables stats collection", + "stats_enable "}, + + {"stats_get", gstd_client_cmd_socket, + "Gets stats collected", + "stats_get"}, + {NULL} }; diff --git a/libgstd/gstd_parser.c b/libgstd/gstd_parser.c index 821984ee..a6cb1d2f 100644 --- a/libgstd/gstd_parser.c +++ b/libgstd/gstd_parser.c @@ -101,6 +101,10 @@ static GstdReturnCode gstd_parser_debug_color (GstdSession *, gchar *, gchar *, gchar **); static GstdReturnCode gstd_parser_debug_reset (GstdSession *, gchar *, gchar *, gchar **); +static GstdReturnCode gstd_parser_stats_enable (GstdSession *, gchar *, + gchar *, gchar **); +static GstdReturnCode gstd_parser_stats_get (GstdSession *, gchar *, + gchar *, gchar **); static GstdReturnCode gstd_parser_pipeline_create_ref (GstdSession *, gchar *, gchar *, gchar **); static GstdReturnCode gstd_parser_pipeline_delete_ref (GstdSession *, gchar *, @@ -159,6 +163,9 @@ static GstdCmd cmds[] = { {"debug_color", gstd_parser_debug_color}, {"debug_reset", gstd_parser_debug_reset}, + {"stats_enable", gstd_parser_stats_enable}, + {"stats_get", gstd_parser_stats_get}, + {"pipeline_create_ref", gstd_parser_pipeline_create_ref}, {"pipeline_delete_ref", gstd_parser_pipeline_delete_ref}, {"pipeline_play_ref", gstd_parser_pipeline_play_ref}, @@ -871,6 +878,42 @@ gstd_parser_debug_reset (GstdSession * session, gchar * action, gchar * reset, return ret; } +static GstdReturnCode +gstd_parser_stats_enable (GstdSession * session, gchar * action, + gchar * enabled, gchar ** response) +{ + GstdReturnCode ret; + gchar *uri; + + g_return_val_if_fail (GSTD_IS_SESSION (session), GSTD_NULL_ARGUMENT); + g_return_val_if_fail (response, GSTD_NULL_ARGUMENT); + + check_argument (enabled, GSTD_BAD_COMMAND); + + uri = g_strdup_printf ("/stats/enable %s", enabled); + ret = gstd_parser_parse_raw_cmd (session, (gchar *) "update", uri, response); + + g_free (uri); + + return ret; +} + +static GstdReturnCode +gstd_parser_stats_get (GstdSession * session, gchar * action, gchar * args, + gchar ** response) +{ + GstdReturnCode ret; + gchar *uri; + + g_return_val_if_fail (GSTD_IS_SESSION (session), GSTD_NULL_ARGUMENT); + g_return_val_if_fail (args, GSTD_NULL_ARGUMENT); + + uri = g_strdup_printf ("/stats/stats"); + ret = gstd_parser_parse_raw_cmd (session, (gchar *) "read", uri, response); + g_free (uri); + + return ret; +} static GstdReturnCode gstd_parser_signal_connect (GstdSession * session, gchar * action, diff --git a/libgstd/gstd_session.c b/libgstd/gstd_session.c index 3412fa89..dd513e0d 100644 --- a/libgstd/gstd_session.c +++ b/libgstd/gstd_session.c @@ -43,6 +43,7 @@ enum PROP_PIPELINES = 1, PROP_PID, PROP_DEBUG, + PROP_STATS, N_PROPERTIES // NOT A PROPERTY }; @@ -120,6 +121,12 @@ gstd_session_class_init (GstdSessionClass * klass) "The debug object containing debug information", GSTD_TYPE_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_STATS] = + g_param_spec_object ("stats", + "Stats", + "The stats object containing tracers information", + GSTD_TYPE_STATS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPERTIES, properties); /* Initialize debug category with nice colors */ @@ -154,6 +161,9 @@ gstd_session_init (GstdSession * self) self->debug = GSTD_DEBUG (g_object_new (GSTD_TYPE_DEBUG, "name", "Debug", NULL)); + self->stats = + GSTD_STATS (g_object_new (GSTD_TYPE_STATS, "name", "Stats", NULL)); + self->pid = (GPid) getpid (); } @@ -176,7 +186,10 @@ gstd_session_get_property (GObject * object, GST_DEBUG_OBJECT (self, "Returning debug object %p", self->debug); g_value_set_object (value, self->debug); break; - + case PROP_STATS: + GST_DEBUG_OBJECT (self, "Returning stats object %p", self->stats); + g_value_set_object (value, self->stats); + break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -199,7 +212,10 @@ gstd_session_set_property (GObject * object, self->debug = g_value_dup_object (value); GST_DEBUG_OBJECT (self, "Changing debug object to %p", self->debug); break; - + case PROP_STATS: + self->stats = g_value_dup_object (value); + GST_DEBUG_OBJECT (self, "Changing stats object to %p", self->stats); + break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -224,6 +240,11 @@ gstd_session_dispose (GObject * object) self->debug = NULL; } + if (self->stats) { + g_object_unref (self->stats); + self->stats = NULL; + } + G_OBJECT_CLASS (gstd_session_parent_class)->dispose (object); } diff --git a/libgstd/gstd_session.h b/libgstd/gstd_session.h index 8f80fdbf..dc0f15c2 100644 --- a/libgstd/gstd_session.h +++ b/libgstd/gstd_session.h @@ -176,6 +176,7 @@ #include "gstd_pipeline.h" #include "gstd_list.h" #include "gstd_debug.h" +#include "gstd_stats.h" G_BEGIN_DECLS #define GSTD_TYPE_SESSION \ @@ -211,6 +212,11 @@ struct _GstdSession * Object containing debug options */ GstdDebug *debug; + + /* + * Object containing stats options + */ + GstdStats *stats; }; struct _GstdSessionClass diff --git a/libgstd/gstd_stats.c b/libgstd/gstd_stats.c new file mode 100644 index 00000000..28f51da1 --- /dev/null +++ b/libgstd/gstd_stats.c @@ -0,0 +1,186 @@ +/* + * This file is part of GStreamer Daemon + * Copyright 2015-2023 Ridgerun, LLC (http://www.ridgerun.com) + * + * 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 + +#include "gstd_stats.h" +#include "gstd_object.h" +#include "gstd_property_reader.h" + +/* Gstd Stats debugging category */ +GST_DEBUG_CATEGORY_STATIC (gstd_stats_cat); + +enum +{ + PROP_ENABLE = 1, + PROP_STATS, + N_PROPERTIES +}; + +#define PROP_ENABLE_DEFAULT FALSE +#define PROP_STATS_DEFAULT NULL + +struct _GstdStats +{ + GstdObject parent; + + /* + * Enables/Disables stats output. + */ + gboolean enable; + + /* + * Current stats + */ + gchar *stats; +}; + +struct _GstdStatsClass +{ + GstdObjectClass parent_class; +}; + +/** + * GstdStats: + * A wrapper for the tracers stats + */ + +G_DEFINE_TYPE (GstdStats, gstd_stats, GSTD_TYPE_OBJECT); + +/* VTable */ +static void gstd_stats_set_property (GObject *, guint, const GValue *, + GParamSpec *); +static void gstd_stats_get_property (GObject *, guint, GValue *, GParamSpec *); +static void gstd_stats_dispose (GObject * obj); + +static void +gstd_stats_class_init (GstdStatsClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + GParamSpec *properties[N_PROPERTIES] = { NULL, }; + guint debug_color; + + oclass->dispose = gstd_stats_dispose; + oclass->get_property = gstd_stats_get_property; + oclass->set_property = gstd_stats_set_property; + + properties[PROP_ENABLE] = + g_param_spec_boolean ("enable", + "Enable", + "Enable stats collection", + PROP_ENABLE_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_STATS] = + g_param_spec_string ("stats", + "Stats", + "Current stats collected", + PROP_STATS_DEFAULT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, N_PROPERTIES, properties); + + /* Initialize debug category with nice colors */ + debug_color = GST_DEBUG_FG_BLACK | GST_DEBUG_BOLD | GST_DEBUG_BG_WHITE; + GST_DEBUG_CATEGORY_INIT (gstd_stats_cat, "gstdstats", debug_color, + "Gstd Stats category"); +} + +static void +gstd_stats_init (GstdStats * self) +{ + GST_INFO_OBJECT (self, "Initializing stats"); + + self->enable = PROP_ENABLE_DEFAULT; + self->stats = PROP_STATS_DEFAULT; + + gstd_object_set_reader (GSTD_OBJECT (self), + g_object_new (GSTD_TYPE_PROPERTY_READER, NULL)); +} + +static void +gstd_stats_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GstdStats *self = GSTD_STATS (object); + + switch (property_id) { + case PROP_ENABLE: + GST_LOG_OBJECT (self, "Returning stats enabled %d", self->enable); + g_value_set_boolean (value, self->enable); + break; + case PROP_STATS: + GST_DEBUG_OBJECT (self, "Returning current stats %s", self->stats); + g_value_set_string (value, self->stats); + break; + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gstd_stats_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GstdStats *self = GSTD_STATS (object); + + switch (property_id) { + case PROP_ENABLE: + self->enable = g_value_get_boolean (value); + GST_DEBUG_OBJECT (self, "Changing stats enabled to %d", self->enable); + break; + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gstd_stats_dispose (GObject * object) +{ + GstdStats *self; + + self = GSTD_STATS (object); + + GST_ERROR_OBJECT (self, "Deinitializing gstd stats"); + + if (self->stats) { + g_free (self->stats); + self->stats = NULL; + } + + G_OBJECT_CLASS (gstd_stats_parent_class)->dispose (object); +} + +GstdStats * +gstd_stats_new (void) +{ + GstdStats *self; + self = GSTD_STATS (g_object_new (GSTD_TYPE_STATS, "name", "stats", NULL)); + + GST_ERROR_OBJECT (self, "New stats object"); + + return self; +} diff --git a/libgstd/gstd_stats.h b/libgstd/gstd_stats.h new file mode 100644 index 00000000..378f56a2 --- /dev/null +++ b/libgstd/gstd_stats.h @@ -0,0 +1,58 @@ +/* + * This file is part of GStreamer Daemon + * Copyright 2015-2023 Ridgerun, LLC (http://www.ridgerun.com) + * + * 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 __GSTD_STATS_H__ +#define __GSTD_STATS_H__ + +#include "gst/gst.h" + +G_BEGIN_DECLS +/* + * Type declaration. + */ +#define GSTD_TYPE_STATS \ + (gstd_stats_get_type()) +#define GSTD_STATS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GSTD_TYPE_STATS,GstdStats)) +#define GSTD_STATS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GSTD_TYPE_STATS,GstdStatsClass)) +#define GSTD_IS_STATS(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GSTD_TYPE_STATS)) +#define GSTD_IS_STATS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GSTD_TYPE_STATS)) +#define GSTD_STATS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GSTD_TYPE_STATS, GstdStatsClass)) +typedef struct _GstdStats GstdStats; +typedef struct _GstdStatsClass GstdStatsClass; + +GType gstd_stats_get_type (void); + +/** + * gstd_stats_new: (constructor) + * + * Creates a new object to handle stats options. + * + * Returns: (transfer full) (nullable): A new #GstdStats. Free after + * usage using g_object_unref() + */ +GstdStats *gstd_stats_new (void); + +G_END_DECLS +#endif // __GSTD_STATS_H__ diff --git a/libgstd/meson.build b/libgstd/meson.build index 214c8f71..a0a69c2d 100644 --- a/libgstd/meson.build +++ b/libgstd/meson.build @@ -47,6 +47,7 @@ gstd_src = [ 'gstd_msg_type.c', 'gstd_bus_msg_qos.c', 'gstd_state.c', + 'gstd_stats.c', 'gstd_parser.c', 'gstd_bus_msg_stream_status.c', 'gstd_bus_msg_element.c', From 4db1e5f1b58b88d4d6801bfd2821f98658172edb Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 18 Jul 2023 11:08:41 -0600 Subject: [PATCH 2/8] Add GstD Stats object This code is based on GStreamer gst-stats application to gather and process stats from tracer logs. Support for stats, rusage and latency tracers were added. --- libgstd/gstd_stats.c | 1022 +++++++++++++++++++++++++++++++++++++++++- libgstd/gstd_stats.h | 2 +- 2 files changed, 1021 insertions(+), 3 deletions(-) diff --git a/libgstd/gstd_stats.c b/libgstd/gstd_stats.c index 28f51da1..1cf128ac 100644 --- a/libgstd/gstd_stats.c +++ b/libgstd/gstd_stats.c @@ -1,6 +1,8 @@ /* * This file is part of GStreamer Daemon - * Copyright 2015-2023 Ridgerun, LLC (http://www.ridgerun.com) + * Based on GStreamer gst-stats application + * + * Copyright 2015-2023 RidgeRun, LLC (http://www.ridgerun.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,10 +25,11 @@ #endif #include +#include -#include "gstd_stats.h" #include "gstd_object.h" #include "gstd_property_reader.h" +#include "gstd_stats.h" /* Gstd Stats debugging category */ GST_DEBUG_CATEGORY_STATIC (gstd_stats_cat); @@ -54,6 +57,32 @@ struct _GstdStats * Current stats */ gchar *stats; + + GRegex *raw_log; + GRegex *ansi_log; + + /* Stats */ + GHashTable *threads; + GPtrArray *elements; + GPtrArray *pads; + GHashTable *latencies; + GHashTable *element_latencies; + GQueue *element_reported_latencies; + guint64 num_buffers; + guint64 num_events; + guint64 num_messages; + guint64 num_queries; + guint num_elements; + guint num_bins; + guint num_pads; + guint num_ghostpads; + GstClockTime last_ts; + guint total_cpuload; + gboolean have_cpuload; + gboolean have_latency; + gboolean have_element_latency; + gboolean have_element_reported_latency; + }; struct _GstdStatsClass @@ -61,6 +90,77 @@ struct _GstdStatsClass GstdObjectClass parent_class; }; +typedef struct +{ + /* display name of the element */ + gchar *name; + /* the number of latencies counted */ + guint64 count; + /* the total of all latencies */ + guint64 total; + /* the min of all latencies */ + guint64 min; + /* the max of all latencies */ + guint64 max; + GstClockTime first_latency_ts; +} GstLatencyStats; + +typedef struct +{ + /* The element name */ + gchar *element; + /* The timestamp of the reported latency */ + guint64 ts; + /* the min reported latency */ + guint64 min; + /* the max reported latency */ + guint64 max; +} GstReportedLatency; + +typedef struct +{ + /* human readable pad name and details */ + gchar *name, *type_name; + guint index; + gboolean is_ghost_pad; + GstPadDirection dir; + /* buffer statistics */ + guint num_buffers; + guint num_live, num_decode_only, num_discont, num_resync, num_corrupted, + num_marker, num_header, num_gap, num_droppable, 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; + /* hierarchy */ + guint parent_ix; +} GstPadStats; + +typedef struct +{ + /* human readable element name */ + gchar *name, *type_name; + guint index; + gboolean is_bin; + /* 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; + /* hierarchy */ + guint parent_ix; +} GstElementStats; + +typedef struct +{ + /* time spend in this thread */ + GstClockTime tthread; + guint cpuload; +} GstThreadStats; + /** * GstdStats: * A wrapper for the tracers stats @@ -73,6 +173,16 @@ static void gstd_stats_set_property (GObject *, guint, const GValue *, GParamSpec *); static void gstd_stats_get_property (GObject *, guint, GValue *, GParamSpec *); static void gstd_stats_dispose (GObject * obj); +static gchar *gstd_stats_get_json (GstdStats * self); +static void gstd_stats_log_monitor (GstDebugCategory * category, + GstDebugLevel level, const gchar * file, const gchar * function, gint line, + GObject * object, GstDebugMessage * message, gpointer user_data); + +static void free_element_stats (gpointer data); +static void free_pad_stats (gpointer data); +static void free_thread_stats (gpointer data); +static void free_latency_stats (gpointer data); +static void free_reported_latency (gpointer data); static void gstd_stats_class_init (GstdStatsClass * klass) @@ -115,6 +225,67 @@ gstd_stats_init (GstdStats * self) gstd_object_set_reader (GSTD_OBJECT (self), g_object_new (GSTD_TYPE_PROPERTY_READER, NULL)); + + /* log parser */ + self->raw_log = g_regex_new ( + /* 1: ts */ + "^([0-9:.]+) +" + /* 2: pid */ + "([0-9]+) +" + /* 3: thread */ + "(0?x?[0-9a-fA-F]+) +" + /* 4: level */ + "([A-Z]+) +" + /* 5: category */ + "([a-zA-Z_-]+) +" + /* 6: file:line:func: */ + "([^:]*:[0-9]+:[^:]*:) +" + /* 7: (obj)? log-text */ + "(.*)$", 0, 0, NULL); + self->ansi_log = g_regex_new ( + /* 1: ts */ + "^([0-9:.]+) +" + /* 2: pid */ + "\\\x1b\\[[0-9;]+m *([0-9]+)\\\x1b\\[00m +" + /* 3: thread */ + "(0x[0-9a-fA-F]+) +" + /* 4: level */ + "(?:\\\x1b\\[[0-9;]+m)?([A-Z]+) +\\\x1b\\[00m +" + /* 5: category */ + "\\\x1b\\[[0-9;]+m +([a-zA-Z_-]+) +" + /* 6: file:line:func: */ + "([^:]*:[0-9]+:[^:]*:)(?:\\\x1b\\[00m)? +" + /* 7: (obj)? log-text */ + "(.*)$", 0, 0, NULL); + + /* global statistics */ + self->threads = g_hash_table_new_full (NULL, NULL, NULL, free_thread_stats); + self->elements = g_ptr_array_new_with_free_func (free_element_stats); + self->pads = g_ptr_array_new_with_free_func (free_pad_stats); + self->latencies = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + free_latency_stats); + self->element_latencies = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + free_latency_stats); + self->element_reported_latencies = g_queue_new ();; + self->num_buffers = 0; + self->num_events = 0; + self->num_messages = 0; + self->num_queries = 0; + self->num_elements = 0; + self->num_bins = 0; + self->num_pads = 0; + self->num_ghostpads = 0; + self->last_ts = G_GUINT64_CONSTANT (0); + self->total_cpuload = 0; + self->have_cpuload = FALSE; + + self->have_latency = FALSE; + self->have_element_latency = FALSE; + self->have_element_reported_latency = FALSE; + + gst_debug_add_log_function (gstd_stats_log_monitor, self, NULL); } static void @@ -129,6 +300,10 @@ gstd_stats_get_property (GObject * object, g_value_set_boolean (value, self->enable); break; case PROP_STATS: + if (self->stats) { + g_free (self->stats); + } + self->stats = gstd_stats_get_json (self); GST_DEBUG_OBJECT (self, "Returning current stats %s", self->stats); g_value_set_string (value, self->stats); break; @@ -171,6 +346,33 @@ gstd_stats_dispose (GObject * object) self->stats = NULL; } + if (self->pads) + g_ptr_array_free (self->pads, TRUE); + if (self->elements) + g_ptr_array_free (self->elements, TRUE); + if (self->threads) + g_hash_table_destroy (self->threads); + + if (self->latencies) { + g_hash_table_remove_all (self->latencies); + g_hash_table_destroy (self->latencies); + self->latencies = NULL; + } + if (self->element_latencies) { + g_hash_table_remove_all (self->element_latencies); + g_hash_table_destroy (self->element_latencies); + self->element_latencies = NULL; + } + if (self->element_reported_latencies) { + g_queue_free_full (self->element_reported_latencies, free_reported_latency); + self->element_reported_latencies = NULL; + } + + if (self->raw_log) + g_regex_unref (self->raw_log); + if (self->ansi_log) + g_regex_unref (self->ansi_log); + G_OBJECT_CLASS (gstd_stats_parent_class)->dispose (object); } @@ -184,3 +386,819 @@ gstd_stats_new (void) return self; } + +/* JSON methods */ +static void +get_pads_json (gpointer data, gpointer user_data) +{ + GstPadStats *stats = NULL; + JsonObject *root = NULL; + JsonObject *new = NULL; + gchar *first_ts = NULL; + gchar *thread_id = NULL; + + g_return_if_fail ((GstPadStats *) data); + g_return_if_fail ((JsonObject *) user_data); + + stats = (GstPadStats *) data; + root = (JsonObject *) user_data; + new = json_object_new (); + + /* Create new pad object */ + first_ts = + g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (stats->first_ts)); + thread_id = g_strdup_printf ("0x%lx", (guint64) stats->thread_id); + json_object_set_string_member (new, "type_name", stats->type_name); + json_object_set_string_member (new, "thread_id", thread_id); + json_object_set_int_member (new, "index", stats->index); + json_object_set_boolean_member (new, "is_ghost_pad", stats->is_ghost_pad); + json_object_set_string_member (new, "direction", + stats->dir == 0 ? "unknown" : (stats->dir == 1 ? "src" : "sink")); + json_object_set_int_member (new, "num_buffers", stats->num_buffers); + json_object_set_int_member (new, "num_live", stats->num_live); + json_object_set_int_member (new, "num_decode_only", stats->num_decode_only); + json_object_set_int_member (new, "num_discont", stats->num_discont); + json_object_set_int_member (new, "num_resync", stats->num_resync); + json_object_set_int_member (new, "num_corrupted", stats->num_corrupted); + json_object_set_int_member (new, "num_marker", stats->num_marker); + json_object_set_int_member (new, "num_header", stats->num_header); + json_object_set_int_member (new, "num_gap", stats->num_gap); + json_object_set_int_member (new, "num_droppable", stats->num_droppable); + json_object_set_int_member (new, "num_delta", stats->num_delta); + json_object_set_int_member (new, "min_size", stats->min_size); + json_object_set_int_member (new, "max_size", stats->max_size); + json_object_set_int_member (new, "avg_size", stats->avg_size); + json_object_set_string_member (new, "first_ts", first_ts); + + /* Add pad object */ + json_object_set_object_member (root, stats->name, new); + + g_free (first_ts); + g_free (thread_id); +} + +static void +get_elements_json (gpointer data, gpointer user_data) +{ + GstElementStats *stats = NULL; + JsonObject *root = NULL; + JsonObject *new = NULL; + gchar *first_ts = NULL; + + g_return_if_fail ((GstElementStats *) data); + g_return_if_fail ((JsonObject *) user_data); + + stats = (GstElementStats *) data; + root = (JsonObject *) user_data; + new = json_object_new (); + + /* Create new element object */ + first_ts = + g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (stats->first_ts)); + json_object_set_string_member (new, "type_name", stats->type_name); + json_object_set_int_member (new, "index", stats->index); + json_object_set_boolean_member (new, "is_bin", stats->is_bin); + json_object_set_int_member (new, "recv_buffers", stats->recv_buffers); + json_object_set_int_member (new, "sent_buffers", stats->sent_buffers); + json_object_set_int_member (new, "recv_bytes", stats->recv_bytes); + json_object_set_int_member (new, "sent_bytes", stats->sent_bytes); + json_object_set_int_member (new, "num_events", stats->num_events); + json_object_set_int_member (new, "num_messages", stats->num_messages); + json_object_set_int_member (new, "num_queries", stats->num_queries); + json_object_set_string_member (new, "first_ts", first_ts); + + /* Add element object */ + json_object_set_object_member (root, stats->name, new); + + g_free (first_ts); +} + +static void +get_threads_json (gpointer key, gpointer value, gpointer user_data) +{ + GstThreadStats *stats = NULL; + JsonObject *root = NULL; + JsonObject *new = NULL; + gchar *id = NULL; + gchar *timestamp = NULL; + + g_return_if_fail ((GstThreadStats *) value); + g_return_if_fail ((JsonObject *) user_data); + + stats = (GstThreadStats *) value; + root = (JsonObject *) user_data; + + /* Create new thread object */ + new = json_object_new (); + timestamp = + g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (stats->tthread)); + id = g_strdup_printf ("0x%lx", (guint64) key); + json_object_set_int_member (new, "cpuload", stats->cpuload); + json_object_set_string_member (new, "timestamp", timestamp); + + /* Add thread object */ + json_object_set_object_member (root, id, new); + + g_free (timestamp); + g_free (id); +} + +static void +get_latencies_json (gpointer key, gpointer value, gpointer user_data) +{ + GstLatencyStats *stats = NULL; + JsonObject *root = NULL; + JsonObject *new = NULL; + gchar *timestamp = NULL; + gchar *min = NULL; + gchar *max = NULL; + gchar *mean = NULL; + + g_return_if_fail ((GstLatencyStats *) value); + g_return_if_fail ((JsonObject *) user_data); + + stats = (GstLatencyStats *) value; + root = (JsonObject *) user_data; + + /* Create new latency object */ + new = json_object_new (); + timestamp = + g_strdup_printf ("%" GST_TIME_FORMAT, + GST_TIME_ARGS (stats->first_latency_ts)); + min = g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (stats->min)); + max = g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (stats->max)); + mean = + g_strdup_printf ("%" GST_TIME_FORMAT, + GST_TIME_ARGS (stats->total / stats->count)); + json_object_set_int_member (new, "buffer_count", stats->count); + json_object_set_string_member (new, "min", min); + json_object_set_string_member (new, "max", max); + json_object_set_string_member (new, "mean", mean); + json_object_set_string_member (new, "first_latency_ts", timestamp); + + /* Add latency object */ + json_object_set_object_member (root, (const gchar *) key, new); + + g_free (timestamp); + g_free (min); + g_free (max); + g_free (mean); +} + +static gchar * +gstd_stats_get_json (GstdStats * self) +{ + gchar *stats = NULL; + JsonObject *stats_root, *elements, *threads, *pads, *latencies; + JsonNode *root_node = NULL; + JsonGenerator *generator = NULL; + + g_return_val_if_fail (self, NULL); + + stats_root = json_object_new (); + + json_object_set_int_member (stats_root, "num_messages", self->num_messages); + json_object_set_int_member (stats_root, "num_buffers", self->num_buffers); + json_object_set_int_member (stats_root, "num_events", self->num_events); + json_object_set_int_member (stats_root, "num_queries", self->num_queries); + json_object_set_int_member (stats_root, "num_elements", self->num_elements); + json_object_set_int_member (stats_root, "num_bins", self->num_bins); + json_object_set_int_member (stats_root, "num_pads", self->num_pads); + json_object_set_int_member (stats_root, "num_ghostpads", self->num_ghostpads); + + if (self->have_cpuload) { + json_object_set_int_member (stats_root, "total_cpuload", + self->total_cpuload); + } + + /* Add elements info */ + elements = json_object_new (); + g_ptr_array_foreach (self->elements, get_elements_json, elements); + json_object_set_object_member (stats_root, "elements", elements); + + /* Add pads info */ + pads = json_object_new (); + g_ptr_array_foreach (self->pads, get_pads_json, pads); + json_object_set_object_member (stats_root, "pads", pads); + + /* Add threads info */ + threads = json_object_new (); + g_hash_table_foreach (self->threads, get_threads_json, threads); + json_object_set_object_member (stats_root, "threads", threads); + + /* Add latency info */ + latencies = json_object_new (); + g_hash_table_foreach (self->latencies, get_latencies_json, latencies); + g_hash_table_foreach (self->element_latencies, get_latencies_json, latencies); + /* TODO: Add element-reported-latencies */ + json_object_set_object_member (stats_root, "latencies", latencies); + + root_node = json_node_new (JSON_NODE_OBJECT); + json_node_set_object (root_node, stats_root); + + generator = json_generator_new (); + json_generator_set_root (generator, root_node); + stats = json_generator_to_data (generator, NULL); + + g_object_unref (generator); + json_node_unref (root_node); + json_object_unref (stats_root); + + return stats; +} + +/* Stats parsing methods */ + +static inline GstElementStats * +get_element_stats (GstdStats * self, guint ix) +{ + GPtrArray *elements = NULL; + + g_return_val_if_fail (self, NULL); + + elements = self->elements; + + return (ix != G_MAXUINT && ix < elements->len) ? + g_ptr_array_index (elements, ix) : NULL; +} + +static void +do_message_stats (GstdStats * self, GstStructure * s) +{ + guint64 ts; + guint elem_ix; + GstElementStats *elem_stats; + + g_return_if_fail (self); + g_return_if_fail (s); + + self->num_messages++; + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, + "element-ix", G_TYPE_UINT, &elem_ix, NULL); + self->last_ts = MAX (self->last_ts, ts); + if (!(elem_stats = get_element_stats (self, elem_ix))) { + GST_WARNING ("no element stats found for ix=%u", elem_ix); + return; + } + elem_stats->num_messages++; +} + +static inline GstPadStats * +get_pad_stats (GstdStats * self, guint ix) +{ + GPtrArray *pads = NULL; + + g_return_val_if_fail (self, NULL); + + pads = self->pads; + + return (ix != G_MAXUINT && ix < pads->len) ? + g_ptr_array_index (pads, ix) : NULL; +} + +static void +do_event_stats (GstdStats * self, GstStructure * s) +{ + guint64 ts; + guint pad_ix, elem_ix; + GstPadStats *pad_stats; + GstElementStats *elem_stats; + + g_return_if_fail (self); + g_return_if_fail (s); + + self->num_events++; + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, + "pad-ix", G_TYPE_UINT, &pad_ix, "element-ix", G_TYPE_UINT, &elem_ix, + NULL); + self->last_ts = MAX (self->last_ts, ts); + if (!(pad_stats = get_pad_stats (self, pad_ix))) { + GST_WARNING ("no pad stats found for ix=%u", pad_ix); + return; + } + if (!(elem_stats = get_element_stats (self, elem_ix))) { + // e.g. reconfigure events are send over unparented pads + GST_INFO ("no element stats found for ix=%u", elem_ix); + return; + } + elem_stats->num_events++; +} + +static void +new_pad_stats (GstdStats * self, GstStructure * s) +{ + GPtrArray *pads; + GstPadStats *stats; + guint ix, parent_ix; + gchar *type, *name; + gboolean is_ghost_pad; + GstPadDirection dir; + guint64 thread_id; + + g_return_if_fail (self); + g_return_if_fail (s); + + pads = self->pads; + + gst_structure_get (s, + "ix", G_TYPE_UINT, &ix, + "parent-ix", G_TYPE_UINT, &parent_ix, + "name", G_TYPE_STRING, &name, + "type", G_TYPE_STRING, &type, + "is-ghostpad", G_TYPE_BOOLEAN, &is_ghost_pad, + "pad-direction", GST_TYPE_PAD_DIRECTION, &dir, + "thread-id", G_TYPE_UINT64, &thread_id, NULL); + + stats = g_slice_new0 (GstPadStats); + if (is_ghost_pad) + self->num_ghostpads++; + self->num_pads++; + stats->name = name; + stats->type_name = type; + stats->index = ix; + stats->is_ghost_pad = is_ghost_pad; + stats->dir = dir; + stats->min_size = G_MAXUINT; + stats->first_ts = stats->last_ts = stats->next_ts = GST_CLOCK_TIME_NONE; + stats->thread_id = (gpointer) (guintptr) thread_id; + stats->parent_ix = parent_ix; + + if (pads->len <= ix) + g_ptr_array_set_size (pads, ix + 1); + g_ptr_array_index (pads, ix) = stats; +} + +static void +new_element_stats (GstdStats * self, GstStructure * s) +{ + GPtrArray *elements; + GstElementStats *stats; + guint ix, parent_ix; + gchar *type, *name; + gboolean is_bin; + + g_return_if_fail (self); + g_return_if_fail (s); + + elements = self->elements; + + gst_structure_get (s, + "ix", G_TYPE_UINT, &ix, + "parent-ix", G_TYPE_UINT, &parent_ix, + "name", G_TYPE_STRING, &name, + "type", G_TYPE_STRING, &type, "is-bin", G_TYPE_BOOLEAN, &is_bin, NULL); + + stats = g_slice_new0 (GstElementStats); + if (is_bin) + self->num_bins++; + self->num_elements++; + stats->index = ix; + stats->name = name; + stats->type_name = type; + stats->is_bin = is_bin; + stats->first_ts = GST_CLOCK_TIME_NONE; + stats->parent_ix = parent_ix; + + if (elements->len <= ix) + g_ptr_array_set_size (elements, ix + 1); + g_ptr_array_index (elements, ix) = stats; +} + +static inline GstThreadStats * +get_thread_stats (GstdStats * self, gpointer id) +{ + GstThreadStats *stats = NULL; + + g_return_val_if_fail (self, NULL); + + stats = g_hash_table_lookup (self->threads, id); + + if (G_UNLIKELY (!stats)) { + stats = g_slice_new0 (GstThreadStats); + stats->tthread = GST_CLOCK_TIME_NONE; + g_hash_table_insert (self->threads, id, stats); + } + return stats; +} + +static void +do_pad_stats (GstdStats * self, GstPadStats * stats, guint elem_ix, guint size, + guint64 ts, guint64 buffer_ts, guint64 buffer_dur, + GstBufferFlags buffer_flags) +{ + gulong avg_size; + + g_return_if_fail (self); + g_return_if_fail (stats); + + /* parentage */ + if (stats->parent_ix == G_MAXUINT) { + stats->parent_ix = elem_ix; + } + + if (stats->thread_id) { + get_thread_stats (self, stats->thread_id); + } + + /* 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 (!GST_CLOCK_TIME_IS_VALID (stats->last_ts)) + stats->first_ts = ts; + stats->last_ts = ts; + /* flag stats */ + if (buffer_flags & GST_BUFFER_FLAG_LIVE) + stats->num_live++; + if (buffer_flags & GST_BUFFER_FLAG_DECODE_ONLY) + stats->num_decode_only++; + if (buffer_flags & GST_BUFFER_FLAG_DISCONT) + stats->num_discont++; + if (buffer_flags & GST_BUFFER_FLAG_RESYNC) + stats->num_resync++; + if (buffer_flags & GST_BUFFER_FLAG_CORRUPTED) + stats->num_corrupted++; + if (buffer_flags & GST_BUFFER_FLAG_MARKER) + stats->num_marker++; + if (buffer_flags & GST_BUFFER_FLAG_HEADER) + stats->num_header++; + if (buffer_flags & GST_BUFFER_FLAG_GAP) + stats->num_gap++; + if (buffer_flags & GST_BUFFER_FLAG_DROPPABLE) + stats->num_droppable++; + if (buffer_flags & GST_BUFFER_FLAG_DELTA_UNIT) + stats->num_delta++; + /* update timestamps */ + if (GST_CLOCK_TIME_IS_VALID (buffer_ts) && + GST_CLOCK_TIME_IS_VALID (buffer_dur)) { + stats->next_ts = buffer_ts + buffer_dur; + } else { + stats->next_ts = GST_CLOCK_TIME_NONE; + } +} + +static void +do_element_stats (GstdStats * self, GstElementStats * stats, + GstElementStats * peer_stats, guint size, guint64 ts) +{ + g_return_if_fail (self); + g_return_if_fail (stats); + g_return_if_fail (peer_stats); + + stats->sent_buffers++; + peer_stats->recv_buffers++; + stats->sent_bytes += size; + peer_stats->recv_bytes += size; + /* time stats */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (stats->first_ts))) { + stats->first_ts = ts; + } + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (peer_stats->first_ts))) { + peer_stats->first_ts = ts + 1; + } +} + +static void +do_buffer_stats (GstdStats * self, GstStructure * s) +{ + guint64 ts; + guint64 buffer_pts = GST_CLOCK_TIME_NONE, buffer_dur = GST_CLOCK_TIME_NONE; + guint pad_ix, elem_ix, peer_elem_ix; + guint size; + GstBufferFlags buffer_flags; + GstPadStats *pad_stats; + GstElementStats *elem_stats = NULL, *peer_elem_stats = NULL; + + g_return_if_fail (self); + g_return_if_fail (s); + + self->num_buffers++; + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, + "pad-ix", G_TYPE_UINT, &pad_ix, + "element-ix", G_TYPE_UINT, &elem_ix, + "peer-element-ix", G_TYPE_UINT, &peer_elem_ix, + "buffer-size", G_TYPE_UINT, &size, + "buffer-flags", GST_TYPE_BUFFER_FLAGS, &buffer_flags, NULL); + gst_structure_get_uint64 (s, "buffer-pts", &buffer_pts); + gst_structure_get_uint64 (s, "buffer-duration", &buffer_dur); + self->last_ts = MAX (self->last_ts, ts); + if (!(pad_stats = get_pad_stats (self, pad_ix))) { + GST_WARNING ("no pad stats found for ix=%u", pad_ix); + return; + } + if (!(elem_stats = get_element_stats (self, elem_ix))) { + GST_WARNING ("no element stats found for ix=%u", elem_ix); + return; + } + if (!(peer_elem_stats = get_element_stats (self, peer_elem_ix))) { + GST_WARNING ("no element stats found for ix=%u", peer_elem_ix); + return; + } + do_pad_stats (self, pad_stats, elem_ix, size, ts, buffer_pts, buffer_dur, + buffer_flags); + if (pad_stats->dir == GST_PAD_SRC) { + /* push */ + do_element_stats (self, elem_stats, peer_elem_stats, size, ts); + } else { + /* pull */ + do_element_stats (self, peer_elem_stats, elem_stats, size, ts); + } +} + +static void +do_query_stats (GstdStats * self, GstStructure * s) +{ + guint64 ts; + guint elem_ix; + GstElementStats *elem_stats = NULL; + + g_return_if_fail (self); + g_return_if_fail (s); + + self->num_queries++; + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, + "element-ix", G_TYPE_UINT, &elem_ix, NULL); + self->last_ts = MAX (self->last_ts, ts); + if (!(elem_stats = get_element_stats (self, elem_ix))) { + GST_WARNING ("no element stats found for ix=%u", elem_ix); + return; + } + elem_stats->num_queries++; +} + +static void +do_thread_rusage_stats (GstdStats * self, GstStructure * s) +{ + guint64 ts, tthread, thread_id; + guint cpuload; + GstThreadStats *thread_stats = NULL; + + g_return_if_fail (self); + g_return_if_fail (s); + + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, + "thread-id", G_TYPE_UINT64, &thread_id, + "average-cpuload", G_TYPE_UINT, &cpuload, "time", G_TYPE_UINT64, &tthread, + NULL); + thread_stats = get_thread_stats (self, (gpointer) (guintptr) thread_id); + thread_stats->cpuload = cpuload; + thread_stats->tthread = tthread; + self->last_ts = MAX (self->last_ts, ts); +} + +static void +do_proc_rusage_stats (GstdStats * self, GstStructure * s) +{ + guint64 ts; + + g_return_if_fail (self); + g_return_if_fail (s); + + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, + "average-cpuload", G_TYPE_UINT, &self->total_cpuload, NULL); + self->last_ts = MAX (self->last_ts, ts); + self->have_cpuload = TRUE; +} + +static void +update_latency_table (GstdStats * self, GHashTable * table, const gchar * key, + guint64 time, GstClockTime ts) +{ + GstLatencyStats *ls = NULL; + + g_return_if_fail (self); + g_return_if_fail (table); + g_return_if_fail (key); + + /* Find the values in the hash table */ + ls = g_hash_table_lookup (table, key); + if (!ls) { + /* Insert the new key if the value does not exist */ + ls = g_new0 (GstLatencyStats, 1); + ls->name = g_strdup (key); + ls->count = 1; + ls->total = time; + ls->min = time; + ls->max = time; + ls->first_latency_ts = ts; + g_hash_table_insert (table, g_strdup (key), ls); + } else { + /* Otherwise update the existing value */ + ls->count++; + ls->total += time; + if (ls->min > time) + ls->min = time; + if (ls->max < time) + ls->max = time; + } +} + +static void +do_latency_stats (GstdStats * self, GstStructure * s) +{ + gchar *key = NULL; + const gchar *src = NULL, *sink = NULL, *src_element = NULL, + *sink_element = NULL, *src_element_id = NULL, *sink_element_id = NULL; + guint64 ts = 0, time = 0; + + g_return_if_fail (self); + g_return_if_fail (s); + + /* Get the values from the structure */ + src = gst_structure_get_string (s, "src"); + sink = gst_structure_get_string (s, "sink"); + src_element = gst_structure_get_string (s, "src-element"); + sink_element = gst_structure_get_string (s, "sink-element"); + src_element_id = gst_structure_get_string (s, "src-element-id"); + sink_element_id = gst_structure_get_string (s, "sink-element-id"); + gst_structure_get (s, "time", G_TYPE_UINT64, &time, NULL); + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, NULL); + + /* Update last_ts */ + self->last_ts = MAX (self->last_ts, ts); + + /* Get the key */ + key = g_strdup_printf ("%s.%s.%s|%s.%s.%s", src_element_id, src_element, + src, sink_element_id, sink_element, sink); + + /* Update the latency in the table */ + update_latency_table (self, self->latencies, key, time, ts); + + /* Clean up */ + g_free (key); + + self->have_latency = TRUE; +} + +static void +do_element_latency_stats (GstdStats * self, GstStructure * s) +{ + gchar *key = NULL; + const gchar *src = NULL, *element = NULL, *element_id = NULL; + guint64 ts = 0, time = 0; + + g_return_if_fail (self); + g_return_if_fail (s); + + /* Get the values from the structure */ + src = gst_structure_get_string (s, "src"); + element = gst_structure_get_string (s, "element"); + element_id = gst_structure_get_string (s, "element-id"); + gst_structure_get (s, "time", G_TYPE_UINT64, &time, NULL); + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, NULL); + + /* Update last_ts */ + self->last_ts = MAX (self->last_ts, ts); + + /* Get the key */ + key = g_strdup_printf ("%s.%s.%s", element_id, element, src); + + /* Update the latency in the table */ + update_latency_table (self, self->element_latencies, key, time, ts); + + /* Clean up */ + g_free (key); + + self->have_element_latency = TRUE; +} + +static void +do_element_reported_latency (GstdStats * self, GstStructure * s) +{ + const gchar *element = NULL, *element_id = NULL; + guint64 ts = 0, min = 0, max = 0; + GstReportedLatency *rl = NULL; + + g_return_if_fail (self); + g_return_if_fail (s); + + /* Get the values from the structure */ + element_id = gst_structure_get_string (s, "element-id"); + element = gst_structure_get_string (s, "element"); + gst_structure_get (s, "min", G_TYPE_UINT64, &min, NULL); + gst_structure_get (s, "max", G_TYPE_UINT64, &max, NULL); + gst_structure_get (s, "ts", G_TYPE_UINT64, &ts, NULL); + + /* Update last_ts */ + self->last_ts = MAX (self->last_ts, ts); + + /* Insert/Update the key in the table */ + rl = g_new0 (GstReportedLatency, 1); + rl->element = g_strdup_printf ("%s.%s", element_id, element); + rl->ts = ts; + rl->min = min; + rl->max = max; + g_queue_push_tail (self->element_reported_latencies, rl); + + self->have_element_reported_latency = TRUE; +} + +static void +gstd_stats_collect_stats (GstdStats * self, GstDebugMessage * message) +{ + const gchar *data = NULL; + GstStructure *s = NULL; + + g_return_if_fail (self); + g_return_if_fail (message); + + data = gst_debug_message_get (message); + if ((s = gst_structure_from_string (data, NULL))) { + const gchar *name = gst_structure_get_name (s); + if (!strcmp (name, "new-pad")) { + new_pad_stats (self, s); + } else if (!strcmp (name, "new-element")) { + new_element_stats (self, s); + } else if (!strcmp (name, "buffer")) { + do_buffer_stats (self, s); + } else if (!strcmp (name, "event")) { + do_event_stats (self, s); + } else if (!strcmp (name, "message")) { + do_message_stats (self, s); + } else if (!strcmp (name, "query")) { + do_query_stats (self, s); + } else if (!strcmp (name, "thread-rusage")) { + do_thread_rusage_stats (self, s); + } else if (!strcmp (name, "proc-rusage")) { + do_proc_rusage_stats (self, s); + } else if (!strcmp (name, "latency")) { + do_latency_stats (self, s); + } else if (!strcmp (name, "element-latency")) { + do_element_latency_stats (self, s); + } else if (!strcmp (name, "element-reported-latency")) { + do_element_reported_latency (self, s); + } else { + /* TODO(ensonic): parse the xxx.class log lines */ + if (!g_str_has_suffix (data, ".class")) { + GST_WARNING ("unknown log entry: '%s'", data); + } + } + gst_structure_free (s); + } else { + GST_WARNING ("unknown log entry: '%s'", data); + } +} + +static void +gstd_stats_log_monitor (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, GObject * object, + GstDebugMessage * message, gpointer user_data) +{ + GstdStats *self = NULL; + + g_return_if_fail (GSTD_STATS (user_data)); + g_return_if_fail (message); + + self = GSTD_STATS (user_data); + + if (self->enable) { + if (GST_LEVEL_TRACE == level) { + gstd_stats_collect_stats (self, message); + } + } +} + +/* Free methods */ + +static void +free_latency_stats (gpointer data) +{ + GstLatencyStats *ls = data; + + g_free (ls->name); + g_slice_free (GstLatencyStats, data); +} + +static void +free_reported_latency (gpointer data) +{ + GstReportedLatency *rl = data; + + if (rl->element) + g_free (rl->element); + + g_free (data); +} + +static void +free_element_stats (gpointer data) +{ + g_slice_free (GstElementStats, data); +} + +static void +free_pad_stats (gpointer data) +{ + g_slice_free (GstPadStats, data); +} + +static void +free_thread_stats (gpointer data) +{ + g_slice_free (GstThreadStats, data); +} diff --git a/libgstd/gstd_stats.h b/libgstd/gstd_stats.h index 378f56a2..a00d07b4 100644 --- a/libgstd/gstd_stats.h +++ b/libgstd/gstd_stats.h @@ -1,6 +1,6 @@ /* * This file is part of GStreamer Daemon - * Copyright 2015-2023 Ridgerun, LLC (http://www.ridgerun.com) + * Copyright 2015-2023 RidgeRun, LLC (http://www.ridgerun.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public From da13bdb7966f4d77d8b9819afedf0720fd9bb2b4 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 18 Jul 2023 11:23:49 -0600 Subject: [PATCH 3/8] Add GstdStats to Autotools --- libgstd/Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libgstd/Makefile.am b/libgstd/Makefile.am index 41bd6267..4f641889 100644 --- a/libgstd/Makefile.am +++ b/libgstd/Makefile.am @@ -58,6 +58,7 @@ libgstd_@GSTD_API_VERSION@_la_SOURCES = \ gstd_signal_reader.c \ gstd_socket.c \ gstd_state.c \ + gstd_stats.c \ gstd_tcp.c \ gstd_unix.c \ libgstd.c @@ -134,5 +135,6 @@ noinst_HEADERS = \ gstd_signal_reader.h \ gstd_socket.h \ gstd_state.h \ + gstd_stats.h \ gstd_tcp.h \ gstd_unix.h From ae6468dc3e9068ba5830f22d32501b6662c8145e Mon Sep 17 00:00:00 2001 From: francis-guindon Date: Fri, 28 Jul 2023 21:51:31 +0200 Subject: [PATCH 4/8] Remove buggy check in get stats method --- libgstd/gstd_parser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libgstd/gstd_parser.c b/libgstd/gstd_parser.c index a6cb1d2f..c1c1b273 100644 --- a/libgstd/gstd_parser.c +++ b/libgstd/gstd_parser.c @@ -906,7 +906,6 @@ gstd_parser_stats_get (GstdSession * session, gchar * action, gchar * args, gchar *uri; g_return_val_if_fail (GSTD_IS_SESSION (session), GSTD_NULL_ARGUMENT); - g_return_val_if_fail (args, GSTD_NULL_ARGUMENT); uri = g_strdup_printf ("/stats/stats"); ret = gstd_parser_parse_raw_cmd (session, (gchar *) "read", uri, response); From bb47eb9b7fbf1fcfede279b8772afda705b1522e Mon Sep 17 00:00:00 2001 From: Francis Guindon Date: Fri, 28 Jul 2023 18:54:42 +0200 Subject: [PATCH 5/8] Add libgstc stats methods --- libgstc/c/libgstc.c | 39 +++++++++++++++++++++++++++++++++++++++ libgstc/c/libgstc.h | 25 +++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/libgstc/c/libgstc.c b/libgstc/c/libgstc.c index 75c1ae3c..2850e678 100644 --- a/libgstc/c/libgstc.c +++ b/libgstc/c/libgstc.c @@ -75,6 +75,8 @@ #define PIPELINE_SIGNAL_TIMEOUT_FORMAT "/pipelines/%s/elements/%s/signals/%s/timeout" #define PIPELINE_SIGNAL_DISCONNECT_FORMAT "/pipelines/%s/elements/%s/signals/%s/disconnect" +#define STATS_ENABLE_FORMAT "stats_enable %s" +#define STATS_GET_FORMAT "stats_get" #define SEEK_FORMAT "seek %f %d %d %d %lld %d %lld" #define FLUSH_STOP_FORMAT "flush_stop %s" #define TIMEOUT_FORMAT "%lli" @@ -1232,3 +1234,40 @@ gstc_pipeline_signal_disconnect (GstClient * client, const char *pipeline_name, return ret; } + +GstcStatus +gstc_enable_stats (GstClient * client, const int enable) +{ + GstcStatus ret; + char *request; + int asprintf_ret; + const char *enable_bool; + + gstc_assert_and_ret_val (NULL != client, GSTC_NULL_ARGUMENT); + + enable_bool = enable == 0 ? "false" : "true"; + asprintf_ret = asprintf (&request, STATS_ENABLE_FORMAT, enable_bool); + if (PRINTF_ERROR == asprintf_ret) { + return GSTC_OOM; + } + ret = gstc_cmd_send (client, request); + + free (request); + + return ret; +} + +GstcStatus +gstc_get_stats (GstClient * client, char **response) +{ + GstcStatus ret; + + gstc_assert_and_ret_val (NULL != client, GSTC_NULL_ARGUMENT); + gstc_assert_and_ret_val (NULL != response, GSTC_NULL_ARGUMENT); + + ret = + gstc_cmd_send_get_response (client, STATS_GET_FORMAT, response, + client->timeout); + + return ret; +} diff --git a/libgstc/c/libgstc.h b/libgstc/c/libgstc.h index 1f28da9b..654b32c8 100644 --- a/libgstc/c/libgstc.h +++ b/libgstc/c/libgstc.h @@ -666,6 +666,31 @@ GstcStatus gstc_pipeline_emit_action (GstClient *client, const char *pipeline_name, const char *element, const char *action); +/** + * gstc_enable_stats: + * @client: The client returned by gstc_client_new() + * @enable: If nonzero, then stats are enabled, if zero stats are disabled + * + * Enables or disables stats collection in the daemon + * + * Returns: GstcStatus indicating success, daemon unreachable, daemon + * timeout + */ +GstcStatus gstc_enable_stats (GstClient *client, const int enable); + +/** + * gstc_get_stats: + * @client: The client returned by gstc_client_new() + * @response: Pointer to output string memory representing a stats value, + * this memory should be freed by the user. + * + * Attempts to get a stats value from gstd + * + * Returns: GstcStatus indicating success, daemon unreachable, daemon + * timeout + */ +GstcStatus gstc_get_stats (GstClient *client, char **response); + #ifdef __cplusplus } #endif From d70ef4fa12165a699ba08cb25819f08007c5cdc2 Mon Sep 17 00:00:00 2001 From: Francis Guindon Date: Fri, 28 Jul 2023 18:50:13 +0200 Subject: [PATCH 6/8] Add test for libgstc stats methods --- tests/libgstc/c/Makefile.am | 7 +- tests/libgstc/c/meson.build | 1 + tests/libgstc/c/test_libgstc_stats.c | 172 +++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 tests/libgstc/c/test_libgstc_stats.c diff --git a/tests/libgstc/c/Makefile.am b/tests/libgstc/c/Makefile.am index 0048ed7a..af5d831b 100644 --- a/tests/libgstc/c/Makefile.am +++ b/tests/libgstc/c/Makefile.am @@ -25,7 +25,8 @@ TESTS = \ libgstc_pipeline_get_state \ libgstc_pipeline_list_signals \ libgstc_pipeline_signal_connect \ - libgstc_pipeline_signal_disconnect + libgstc_pipeline_signal_disconnect \ + libgstc_enable_stats check_PROGRAMS = $(TESTS) @@ -175,3 +176,7 @@ libgstc_pipeline_list_signals_SOURCES = \ @top_srcdir@/libgstc/c/libgstc.c \ $(COMMON_SOURCES) +libgstc_stats_SOURCES = \ + test_libgstc_stats.c \ + @top_srcdir@/libgstc/c/libgstc.c \ + $(COMMON_SOURCES) diff --git a/tests/libgstc/c/meson.build b/tests/libgstc/c/meson.build index ca07cc95..f8800295 100644 --- a/tests/libgstc/c/meson.build +++ b/tests/libgstc/c/meson.build @@ -25,6 +25,7 @@ lib_gstc_tests = [ ['test_libgstc_pipeline_list_signals.c'], ['test_libgstc_pipeline_signal_connect.c'], ['test_libgstc_pipeline_signal_disconnect.c'], + ['test_libgstc_stats.c'], ] # These are specials tests since is required to re-compile libgstc diff --git a/tests/libgstc/c/test_libgstc_stats.c b/tests/libgstc/c/test_libgstc_stats.c new file mode 100644 index 00000000..43f4f6bb --- /dev/null +++ b/tests/libgstc/c/test_libgstc_stats.c @@ -0,0 +1,172 @@ +/* + * This file is part of GStreamer Daemon + * Copyright 2015-2023 Ridgerun, LLC (http://www.ridgerun.com) + * + * 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. + */ +#include +#include + +#include "libgstc.h" +#include "libgstc_json.h" +#include "libgstc_socket.h" +#include "libgstc_assert.h" + +/* Test Fixture */ +static gchar _request[512]; +static GstClient *_client; + +static void +setup (void) +{ + const gchar *address = ""; + unsigned int port = 0; + unsigned long wait_time = 5; + int keep_connection_open = 0; + + gstc_client_new (address, port, wait_time, keep_connection_open, &_client); +} + +static void +teardown (void) +{ + gstc_client_free (_client); +} + +/* Mock implementation of a socket */ +typedef struct _GstcSocket +{ +} GstcSocket; + +GstcSocket _socket; + +GstcStatus +gstc_socket_new (const char *address, const unsigned int port, + const int keep_connection_open, GstcSocket ** out) +{ + *out = &_socket; + + return GSTC_OK; +} + +void +gstc_socket_free (GstcSocket * socket) +{ +} + +GstcStatus +gstc_socket_send (GstcSocket * socket, const gchar * request, gchar ** response, + const int timeout) +{ + *response = malloc (1); + + memcpy (_request, request, strlen (request)); + + return GSTC_OK; +} + +GstcStatus +gstc_json_get_int (const gchar * json, const gchar * name, gint * out) +{ + return *out = GSTC_OK; +} + +GstcStatus +gstc_json_is_null (const gchar * json, const gchar * name, gint * out) +{ + *out = 0; + return GSTC_OK; +} + +GstcStatus +gstc_json_get_child_char_array (const char *json, const char *parent_name, + const char *array_name, const char *element_name, char **out[], + int *array_lenght) +{ + return GSTC_OK; +} + +GstcStatus +gstc_json_child_string (const char *json, const char *parent_name, + const char *data_name, char **out) +{ + gstc_assert_and_ret_val (NULL != json, GSTC_NULL_ARGUMENT); + gstc_assert_and_ret_val (NULL != parent_name, GSTC_NULL_ARGUMENT); + gstc_assert_and_ret_val (NULL != data_name, GSTC_NULL_ARGUMENT); + gstc_assert_and_ret_val (NULL != out, GSTC_NULL_ARGUMENT); + + return GSTC_OK; +} + +GST_START_TEST (test_enable_stats) +{ + GstcStatus ret; + const int true_int = 1; + const gchar *expected = "stats_enable true"; + + ret = gstc_enable_stats (_client, true_int); + assert_equals_int (GSTC_OK, ret); + + assert_equals_string (expected, _request); +} + +GST_END_TEST; + +GST_START_TEST (test_disable_stats) +{ + GstcStatus ret; + const int false_int = 0; + const gchar *expected = "stats_enable false"; + + ret = gstc_enable_stats (_client, false_int); + assert_equals_int (GSTC_OK, ret); + + assert_equals_string (expected, _request); +} + +GST_END_TEST; + +GST_START_TEST (test_stats_get) +{ + GstcStatus ret; + const gchar *expected = "stats_get"; + gchar *response = NULL; + + ret = gstc_get_stats (_client, &response); + assert_equals_int (GSTC_OK, ret); + + assert_equals_string (expected, _request); +} + +GST_END_TEST; + +static Suite * +libgstc_stats_suite (void) +{ + Suite *suite = suite_create ("libgstc_stats"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (suite, tc); + + tcase_add_checked_fixture (tc, setup, teardown); + tcase_add_test (tc, test_enable_stats); + tcase_add_test (tc, test_disable_stats); + tcase_add_test (tc, test_stats_get); + + return suite; +} + +GST_CHECK_MAIN (libgstc_stats); From 4381a8c31f18d6c162dd424b1ff34c2b3b954f81 Mon Sep 17 00:00:00 2001 From: francis-guindon Date: Tue, 22 Aug 2023 19:27:12 +0200 Subject: [PATCH 7/8] Add reset stats method to gstd --- gst_client/gst_client.c | 4 +++ libgstd/gstd_parser.c | 19 ++++++++++ libgstd/gstd_stats.c | 78 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/gst_client/gst_client.c b/gst_client/gst_client.c index 1d0554d9..18d0a002 100644 --- a/gst_client/gst_client.c +++ b/gst_client/gst_client.c @@ -211,6 +211,10 @@ static GstdClientCmd cmds[] = { "Gets stats collected", "stats_get"}, + {"stats_reset", gstd_client_cmd_socket, + "Resets stats inner state", + "stats_reset"}, + {NULL} }; diff --git a/libgstd/gstd_parser.c b/libgstd/gstd_parser.c index c1c1b273..334cc68e 100644 --- a/libgstd/gstd_parser.c +++ b/libgstd/gstd_parser.c @@ -105,6 +105,8 @@ static GstdReturnCode gstd_parser_stats_enable (GstdSession *, gchar *, gchar *, gchar **); static GstdReturnCode gstd_parser_stats_get (GstdSession *, gchar *, gchar *, gchar **); +static GstdReturnCode gstd_parser_stats_reset (GstdSession *, gchar *, + gchar *, gchar **); static GstdReturnCode gstd_parser_pipeline_create_ref (GstdSession *, gchar *, gchar *, gchar **); static GstdReturnCode gstd_parser_pipeline_delete_ref (GstdSession *, gchar *, @@ -165,6 +167,7 @@ static GstdCmd cmds[] = { {"stats_enable", gstd_parser_stats_enable}, {"stats_get", gstd_parser_stats_get}, + {"stats_reset", gstd_parser_stats_reset}, {"pipeline_create_ref", gstd_parser_pipeline_create_ref}, {"pipeline_delete_ref", gstd_parser_pipeline_delete_ref}, @@ -914,6 +917,22 @@ gstd_parser_stats_get (GstdSession * session, gchar * action, gchar * args, return ret; } +static GstdReturnCode +gstd_parser_stats_reset (GstdSession * session, gchar * action, gchar * args, + gchar ** response) +{ + GstdReturnCode ret; + gchar *uri; + + g_return_val_if_fail (GSTD_IS_SESSION (session), GSTD_NULL_ARGUMENT); + + uri = g_strdup_printf ("/stats/reset"); + ret = gstd_parser_parse_raw_cmd (session, (gchar *) "read", uri, response); + g_free (uri); + + return ret; +} + static GstdReturnCode gstd_parser_signal_connect (GstdSession * session, gchar * action, gchar * args, gchar ** response) diff --git a/libgstd/gstd_stats.c b/libgstd/gstd_stats.c index 1cf128ac..59c15429 100644 --- a/libgstd/gstd_stats.c +++ b/libgstd/gstd_stats.c @@ -1,7 +1,7 @@ /* * This file is part of GStreamer Daemon * Based on GStreamer gst-stats application - * + * * Copyright 2015-2023 RidgeRun, LLC (http://www.ridgerun.com) * * This library is free software; you can redistribute it and/or @@ -38,11 +38,13 @@ enum { PROP_ENABLE = 1, PROP_STATS, + PROP_RESET, N_PROPERTIES }; #define PROP_ENABLE_DEFAULT FALSE #define PROP_STATS_DEFAULT NULL +#define PROP_RESET_DEFAULT NULL struct _GstdStats { @@ -177,6 +179,7 @@ static gchar *gstd_stats_get_json (GstdStats * self); static void gstd_stats_log_monitor (GstDebugCategory * category, GstDebugLevel level, const gchar * file, const gchar * function, gint line, GObject * object, GstDebugMessage * message, gpointer user_data); +static void gstd_stats_reset (GstdStats * self); static void free_element_stats (gpointer data); static void free_pad_stats (gpointer data); @@ -207,6 +210,12 @@ gstd_stats_class_init (GstdStatsClass * klass) "Current stats collected", PROP_STATS_DEFAULT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_RESET] = + g_param_spec_string ("reset", + "Reset", + "Reset inner state", + PROP_RESET_DEFAULT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (oclass, N_PROPERTIES, properties); /* Initialize debug category with nice colors */ @@ -307,6 +316,10 @@ gstd_stats_get_property (GObject * object, GST_DEBUG_OBJECT (self, "Returning current stats %s", self->stats); g_value_set_string (value, self->stats); break; + case PROP_RESET: + GST_DEBUG_OBJECT (self, "Reseting stats"); + gstd_stats_reset (self); + break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -1163,6 +1176,69 @@ gstd_stats_log_monitor (GstDebugCategory * category, GstDebugLevel level, } } +static void +gstd_stats_reset (GstdStats * self) +{ + g_return_if_fail (self); + + if (self->stats) { + g_free (self->stats); + } + + self->stats = PROP_STATS_DEFAULT; + + if (self->pads) + g_ptr_array_free (self->pads, TRUE); + if (self->elements) + g_ptr_array_free (self->elements, TRUE); + if (self->threads) { + g_hash_table_destroy (self->threads); + } + + self->threads = g_hash_table_new_full (NULL, NULL, NULL, free_thread_stats); + self->elements = g_ptr_array_new_with_free_func (free_element_stats); + self->pads = g_ptr_array_new_with_free_func (free_pad_stats); + + if (self->latencies) { + g_hash_table_remove_all (self->latencies); + g_hash_table_destroy (self->latencies); + self->latencies = NULL; + } + if (self->element_latencies) { + g_hash_table_remove_all (self->element_latencies); + g_hash_table_destroy (self->element_latencies); + self->element_latencies = NULL; + } + if (self->element_reported_latencies) { + g_queue_free_full (self->element_reported_latencies, free_reported_latency); + self->element_reported_latencies = NULL; + } + + self->latencies = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + free_latency_stats); + self->element_latencies = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + free_latency_stats); + self->element_reported_latencies = g_queue_new (); + + self->num_buffers = 0; + self->num_events = 0; + self->num_messages = 0; + self->num_queries = 0; + self->num_elements = 0; + self->num_bins = 0; + self->num_pads = 0; + self->num_ghostpads = 0; + self->last_ts = G_GUINT64_CONSTANT (0); + self->total_cpuload = 0; + self->have_cpuload = FALSE; + + self->have_latency = FALSE; + self->have_element_latency = FALSE; + self->have_element_reported_latency = FALSE; +} + /* Free methods */ static void From bc4980140cef3bbb2e15b9ca9f875292b5748f23 Mon Sep 17 00:00:00 2001 From: francis-guindon Date: Tue, 22 Aug 2023 19:27:40 +0200 Subject: [PATCH 8/8] Add reset stats method to libgstc --- libgstc/c/libgstc.c | 15 ++++++++++++++- libgstc/c/libgstc.h | 29 ++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/libgstc/c/libgstc.c b/libgstc/c/libgstc.c index 2850e678..dc2f07e0 100644 --- a/libgstc/c/libgstc.c +++ b/libgstc/c/libgstc.c @@ -76,7 +76,8 @@ #define PIPELINE_SIGNAL_DISCONNECT_FORMAT "/pipelines/%s/elements/%s/signals/%s/disconnect" #define STATS_ENABLE_FORMAT "stats_enable %s" -#define STATS_GET_FORMAT "stats_get" +#define STATS_GET_FORMAT "stats_get" +#define STATS_RESET_FORMAT "stats_reset" #define SEEK_FORMAT "seek %f %d %d %d %lld %d %lld" #define FLUSH_STOP_FORMAT "flush_stop %s" #define TIMEOUT_FORMAT "%lli" @@ -1271,3 +1272,15 @@ gstc_get_stats (GstClient * client, char **response) return ret; } + +GstcStatus +gstc_reset_stats (GstClient * client) +{ + GstcStatus ret; + + gstc_assert_and_ret_val (NULL != client, GSTC_NULL_ARGUMENT); + + ret = gstc_cmd_send (client, STATS_RESET_FORMAT); + + return ret; +} diff --git a/libgstc/c/libgstc.h b/libgstc/c/libgstc.h index 654b32c8..3071292e 100644 --- a/libgstc/c/libgstc.h +++ b/libgstc/c/libgstc.h @@ -181,7 +181,7 @@ GstcStatus gstc_client_ping(GstClient *client); * @threshold: the debug level takes a keyword and the debug level in the argument * recieving 0 as a level is equivalent to disabling debug * @colors: if non-zero ANSI color control escape sequences will be included in the debug output - * @reset: if non-zero the debug threshold will be cleared each time, otherwise threshold + * @reset: if non-zero the debug threshold will be cleared each time, otherwise threshold * is appended to previous threshold. * * Controls amount of GStreamer Daemon debug logging. Typically the GStreamer Daemon debug log output is directed to the system log file. @@ -398,7 +398,7 @@ GstcStatus gstc_element_get (GstClient *client, const char *pname, */ GstcStatus gstc_element_set(GstClient *client, const char *pname, const char *element, const char *parameter, const char *format, ...); - + /** * gstc_element_properties_list: * @client: The client returned by gstc_client_new() @@ -530,7 +530,7 @@ GstcStatus gstc_pipeline_list_elements(GstClient *client, const char *pipeline_n * @timeout: The amount of nanoseconds to wait for the event, or -1 * for unlimited * @user_data: (allow none): A placeholder for custom data - * + * * The callback signature of the function to be registered in * gst_pipeline_bus_wait_async(). * @@ -551,7 +551,7 @@ typedef GstcStatus * @callback: The function to be called when the message (or timeout) * is received on the bus. * @user_data: (allow none): A placeholder for custom data - * + * * Register a callback function to be called when a specific message * is received on the bus or a timeout ocurred. * @@ -570,7 +570,7 @@ gstc_pipeline_bus_wait_async (GstClient *client, * @message_name: The type of message to receive * @timeout: The amount of nanoseconds to wait for the event, or -1 * for unlimited - * + * * Block until a message of type @message_name is received in the bus * or a timeout occurs. * @@ -670,9 +670,9 @@ GstcStatus gstc_pipeline_emit_action (GstClient *client, * gstc_enable_stats: * @client: The client returned by gstc_client_new() * @enable: If nonzero, then stats are enabled, if zero stats are disabled - * + * * Enables or disables stats collection in the daemon - * + * * Returns: GstcStatus indicating success, daemon unreachable, daemon * timeout */ @@ -683,14 +683,25 @@ GstcStatus gstc_enable_stats (GstClient *client, const int enable); * @client: The client returned by gstc_client_new() * @response: Pointer to output string memory representing a stats value, * this memory should be freed by the user. - * + * * Attempts to get a stats value from gstd - * + * * Returns: GstcStatus indicating success, daemon unreachable, daemon * timeout */ GstcStatus gstc_get_stats (GstClient *client, char **response); +/** + * gstc_reset_stats: + * @client: The client returned by gstc_client_new() + * + * Resets the stats state of the current gstd session + * + * Returns: GstcStatus indicating success, daemon unreachable, daemon + * timeout + */ +GstcStatus gstc_reset_stats (GstClient *client); + #ifdef __cplusplus } #endif