From: Sangchul Lee Date: Tue, 10 Jan 2023 09:12:41 +0000 (+0900) Subject: webrtc_stats: Dump stats result into the file X-Git-Tag: accepted/tizen/unified/20230213.170658^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=828e922a1821df49979a110636ab95ee4f240768;p=platform%2Fcore%2Fapi%2Fwebrtc.git webrtc_stats: Dump stats result into the file 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.286 [Issue type] New feature Change-Id: If66b7936166fdb9412f748f38a5374fd472df677 --- diff --git a/packaging/capi-media-webrtc.spec b/packaging/capi-media-webrtc.spec index 26ec27fc..e53afae4 100644 --- a/packaging/capi-media-webrtc.spec +++ b/packaging/capi-media-webrtc.spec @@ -1,6 +1,6 @@ Name: capi-media-webrtc Summary: A WebRTC library in Tizen Native API -Version: 0.3.285 +Version: 0.3.286 Release: 0 Group: Multimedia/API License: Apache-2.0 diff --git a/src/webrtc_stats.c b/src/webrtc_stats.c index 4aba7ae3..a7c32a3e 100644 --- a/src/webrtc_stats.c +++ b/src/webrtc_stats.c @@ -16,12 +16,16 @@ #include "webrtc.h" #include "webrtc_private.h" +#include +#include //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