#include "webrtc.h"
#include "webrtc_private.h"
+#include <json-glib/json-glib.h>
+#include <glib/gstdio.h>
//LCOV_EXCL_START
#define FOREACH_STATS_TIMEOUT_SEC 1
#define WEBRTC_STATS_TYPE_GST_ALL 0xFFFF /* All statistics types of webrtcbin for debugging */
#define WEBRTC_STATS_PROP_NOT_EXPORTED 0x0 /* Statistics property that should not be exported */
+static GList *g_stats_dump_files;
+
typedef struct _stats_field_s {
const gchar *name;
int prop;
webrtc_s *webrtc;
int type_mask;
webrtc_callbacks_s stats_cb;
+ JsonObject *dump;
} promise_userdata_s;
typedef struct _stats_userdata_s {
int type;
stats_field_s **fields_list;
bool export;
+ JsonObject *sub_dump;
} stats_userdata_s;
static gboolean __invoke_stats_cb(int type, const gchar *field_name, webrtc_stats_prop_e prop, webrtc_stats_prop_type_e data_type, void *result, webrtc_callbacks_s *stats_cb)
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_boolean_member(stats->sub_dump, field_name, result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_BOOL, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_INT: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_int_member(stats->sub_dump, field_name, (gint64)result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_INT, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_INT64: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_int_member(stats->sub_dump, field_name, result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_INT64, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_UINT: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_int_member(stats->sub_dump, field_name, (gint64)result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_UINT, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_UINT64: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_int_member(stats->sub_dump, field_name, (gint64)result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_UINT64, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_FLOAT: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_double_member(stats->sub_dump, field_name, (gdouble)result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_FLOAT, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_DOUBLE: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_double_member(stats->sub_dump, field_name, result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_DOUBLE, &result, &stats->p_userdata->stats_cb);
}
case G_TYPE_STRING: {
if (prop == WEBRTC_STATS_PROP_NOT_EXPORTED)
goto skip_callback;
+ if (stats->sub_dump)
+ json_object_set_string_member(stats->sub_dump, field_name, result);
+
return __invoke_stats_cb(stats->type, field_name, prop, WEBRTC_STATS_PROP_TYPE_STRING, (void *)result, &stats->p_userdata->stats_cb);
}
default:
if (!g_strcmp0(g_type_name(G_VALUE_TYPE(val)), "GstWebRTCStatsType")) {
LOG_DEBUG("field[%23s] GType[%18s] value[%21d]",
field_name, g_type_name(G_VALUE_TYPE(val)), g_value_get_enum(val));
+ if (stats->sub_dump)
+ json_object_set_int_member(stats->sub_dump, field_name, (gint64)g_value_get_enum(val));
break;
}
LOG_ERROR("invalid type, field[%23s] GType[%18s]", field_name, g_type_name(G_VALUE_TYPE(val)));
static gboolean __stats_codec_invoke_callback(const GstStructure *s, webrtc_stats_type_e type, stats_field_s **fields_list, promise_userdata_s *user_data)
{
stats_userdata_s stats_userdata = { .p_userdata = user_data, .type = type, .fields_list = fields_list, .export = true };
+ gboolean ret;
RET_VAL_IF(user_data == NULL, FALSE, "user_data is NULL");
LOG_DEBUG_ENTER();
- return gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+ if (user_data->dump)
+ stats_userdata.sub_dump = json_object_new();
+
+ ret = gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+
+ if (user_data->dump)
+ json_object_set_object_member(user_data->dump, "codec", stats_userdata.sub_dump);
+
+ return ret;
}
static gboolean __stats_inbound_rtp_invoke_callback(const GstStructure *s, webrtc_stats_type_e type, stats_field_s **fields_list, promise_userdata_s *user_data)
stats_userdata_s stats_userdata = { .p_userdata = user_data, .type = type, .fields_list = fields_list, .export = true };
g_autoptr(GstStructure) rtpjitterbuffer_stats = NULL;
g_autoptr(GstStructure) rtpsource_stats = NULL;
+ gboolean ret;
RET_VAL_IF(user_data == NULL, FALSE, "user_data is NULL");
LOG_DEBUG_ENTER();
- if (!gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata))
- return FALSE;
+ if (user_data->dump)
+ stats_userdata.sub_dump = json_object_new();
+
+ ret = gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+
+ if (user_data->dump)
+ json_object_set_object_member(user_data->dump, "inbound-rtp", stats_userdata.sub_dump);
+
+ if (!ret)
+ return ret;
gst_structure_get(s,
"gst-rtpjitterbuffer-stats", GST_TYPE_STRUCTURE, &rtpjitterbuffer_stats,
{
stats_userdata_s stats_userdata = { .p_userdata = user_data, .type = type, .fields_list = fields_list, .export = true };
g_autoptr(GstStructure) rtpsource_stats = NULL;
+ gboolean ret;
RET_VAL_IF(user_data == NULL, FALSE, "user_data is NULL");
LOG_DEBUG_ENTER();
- if (!gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata))
- return FALSE;
+ if (user_data->dump)
+ stats_userdata.sub_dump = json_object_new();
+
+ ret = gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+
+ if (user_data->dump)
+ json_object_set_object_member(user_data->dump, "outbound-rtp", stats_userdata.sub_dump);
+
+ if (!ret)
+ return ret;
stats_userdata.export = false; /* to skip invoking callback stats below */
static gboolean __stats_remote_inbound_rtp_invoke_callback(const GstStructure *s, webrtc_stats_type_e type, stats_field_s **fields_list, promise_userdata_s *user_data)
{
stats_userdata_s stats_userdata = { .p_userdata = user_data, .type = type, .fields_list = fields_list, .export = true };
+ gboolean ret;
+
RET_VAL_IF(user_data == NULL, FALSE, "user_data is NULL");
LOG_DEBUG_ENTER();
- return gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+ if (user_data->dump)
+ stats_userdata.sub_dump = json_object_new();
+
+ ret = gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+
+ if (user_data->dump)
+ json_object_set_object_member(user_data->dump, "remote-inbound-rtp", stats_userdata.sub_dump);
+
+ return ret;
}
static gboolean __stats_remote_outbound_rtp_invoke_callback(const GstStructure *s, webrtc_stats_type_e type, stats_field_s **fields_list, promise_userdata_s *user_data)
{
stats_userdata_s stats_userdata = { .p_userdata = user_data, .type = type, .fields_list = fields_list, .export = true };
RET_VAL_IF(user_data == NULL, FALSE, "user_data is NULL");
+ gboolean ret;
LOG_DEBUG_ENTER();
- return gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+ if (user_data->dump)
+ stats_userdata.sub_dump = json_object_new();
+
+ ret = gst_structure_foreach(s, __stats_field_foreach_cb, &stats_userdata);
+
+ if (user_data->dump)
+ json_object_set_object_member(user_data->dump, "remote-outbound-rtp", stats_userdata.sub_dump);
+
+ return ret;
}
static gboolean __stats_csrc_invoke_callback(const GstStructure *s, webrtc_stats_type_e type, stats_field_s **fields_list, promise_userdata_s *user_data)
g_mutex_unlock(&data->webrtc->stats_mutex);
}
+//LCOV_EXCL_START
+static void __dump_json_object_to_file(JsonObject *object, const gchar *path)
+{
+ JsonNode *root;
+ JsonGenerator *generator;
+ g_autoptr(GError) error = NULL;
+
+ RET_IF(object == NULL, "object is NULL");
+ RET_IF(path == NULL, "path is NULL");
+
+ root = json_node_init_object(json_node_alloc(), object);
+ generator = json_generator_new();
+ json_generator_set_root(generator, root);
+ if (!json_generator_to_file(generator, path, &error))
+ LOG_ERROR("failed to json_generator_to_file() for %s, error:%s", path, error->message);
+
+ g_object_unref(generator);
+ json_node_free(root);
+}
+
+static gint __str_equal(gconstpointer a, gconstpointer b)
+{
+ return g_strcmp0((const gchar *)a, (const gchar *)b);
+}
+
void _webrtcbin_foreach_stats(webrtc_s *webrtc, int type_mask, void *callback, void *user_data)
{
GstPromise *promise;
promise_userdata_s *userdata;
gint64 end_time = g_get_monotonic_time() + FOREACH_STATS_TIMEOUT_SEC * G_TIME_SPAN_SECOND;
+ bool dump_stats = false;
+ JsonObject *dump;
RET_IF(webrtc == NULL, "webrtc is NULL");
RET_IF(webrtc->gst.webrtcbin == NULL, "webrtcbin is NULL");
if (callback) {
userdata->stats_cb.callback = callback;
userdata->stats_cb.user_data = user_data;
+ } else {
+ /* this should be triggered by ini setting as of now, "stats log period" */
+ dump_stats = true;
+ dump = json_object_new();
+ userdata->dump = dump;
}
promise = gst_promise_new_with_change_func((GstPromiseChangeFunc)__webrtcbin_get_stats_cb, userdata, g_free);
if (!g_cond_wait_until(&webrtc->stats_cond, &webrtc->stats_mutex, end_time))
LOG_ERROR("failed to get stats within %d sec. of timeout", FOREACH_STATS_TIMEOUT_SEC);
g_mutex_unlock(&webrtc->stats_mutex);
+
+ if (dump_stats) {
+ g_autofree gchar *path = g_strdup_printf("/tmp/webrtc-stats-%d-%p.dump", getpid(), webrtc);
+ __dump_json_object_to_file(dump, path);
+ if (!g_list_find_custom(g_stats_dump_files, path, __str_equal))
+ g_stats_dump_files = g_list_append(g_stats_dump_files, g_steal_pointer(&path));
+ json_object_unref(dump);
+ }
}
static gboolean __get_stats_periodically(gpointer user_data)
if (__stats_all_fields_list[i][j].id == 0)
__stats_all_fields_list[i][j].id = g_quark_from_string(__stats_all_fields_list[i][j].name);
}
+
+__attribute__ ((destructor))
+static void __finalize(void)
+{
+ GList *list;
+
+ for (list = g_stats_dump_files; list; list = g_list_next(list)) {
+ if (g_remove((const gchar *)list->data) == -1)
+ LOG_ERROR("failed to remove %s", (const gchar *)list->data);
+ }
+ g_list_free_full(g_stats_dump_files, g_free);
+}
//LCOV_EXCL_STOP