webrtc_stats: Dump stats result into the file 31/288031/1 accepted/tizen/7.0/unified/20230213.171744
authorSangchul Lee <sc11.lee@samsung.com>
Tue, 10 Jan 2023 09:12:41 +0000 (18:12 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Thu, 9 Feb 2023 08:25:36 +0000 (17:25 +0900)
The JSON data of stats result will be written into
/tmp/webrtc-stats-[pid]-[handle address].dump
if 'stats log period' field in the ini file is set to some values.

[Version] 0.3.287
[Issue type] New feature

Change-Id: If66b7936166fdb9412f748f38a5374fd472df677

packaging/capi-media-webrtc.spec
src/webrtc_stats.c

index e53afae44b3c79fd00ee174213e46bca81bcba63..5fdeb87f3088c36029a92801c750706bdf6a3bea 100644 (file)
@@ -1,6 +1,6 @@
 Name:       capi-media-webrtc
 Summary:    A WebRTC library in Tizen Native API
-Version:    0.3.286
+Version:    0.3.287
 Release:    0
 Group:      Multimedia/API
 License:    Apache-2.0
index 4aba7ae3b20e72fc4a47b9bbd999bf6571305b0f..a7c32a3e4d0b47ea0feee5417d5c0144155ba1e6 100644 (file)
 
 #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;
@@ -270,6 +274,7 @@ typedef struct _promise_userdata_s {
        webrtc_s *webrtc;
        int type_mask;
        webrtc_callbacks_s stats_cb;
+       JsonObject *dump;
 } promise_userdata_s;
 
 typedef struct _stats_userdata_s {
@@ -277,6 +282,7 @@ 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)
@@ -367,6 +373,9 @@ invoke_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: {
@@ -376,6 +385,9 @@ invoke_cb:
                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: {
@@ -385,6 +397,9 @@ invoke_cb:
                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: {
@@ -394,6 +409,9 @@ invoke_cb:
                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: {
@@ -403,6 +421,9 @@ invoke_cb:
                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: {
@@ -412,6 +433,9 @@ invoke_cb:
                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: {
@@ -421,6 +445,9 @@ invoke_cb:
                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: {
@@ -430,12 +457,17 @@ invoke_cb:
                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)));
@@ -452,12 +484,21 @@ skip_callback:
 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)
@@ -465,13 +506,22 @@ static gboolean __stats_inbound_rtp_invoke_callback(const GstStructure *s, webrt
        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,
@@ -498,13 +548,22 @@ static gboolean __stats_outbound_rtp_invoke_callback(const GstStructure *s, webr
 {
        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 */
 
@@ -521,21 +580,40 @@ static gboolean __stats_outbound_rtp_invoke_callback(const GstStructure *s, webr
 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)
@@ -737,11 +815,38 @@ static void __webrtcbin_get_stats_cb(GstPromise *promise, gpointer 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");
@@ -754,6 +859,11 @@ void _webrtcbin_foreach_stats(webrtc_s *webrtc, int type_mask, void *callback, v
        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);
@@ -766,6 +876,14 @@ void _webrtcbin_foreach_stats(webrtc_s *webrtc, int type_mask, void *callback, v
        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)
@@ -815,4 +933,16 @@ void _init_stats_all_fields_list(void)
                        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