mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-07 23:12:42 +00:00
41e35c334b
The goal of this tracer is to measure the processing latency between a src and a sink. In push mode, the time was read after the chain function have returned. As the amount of time we wait to get synched is reverse to the amount of latency the source introduced, the result was quite surprising. This patch moves the latency calculation in the pre-push hook. When there is no processing in a a pipeline (e.g. fakesrc ! fakesink), the latency will now be 0 as it's supposed to. For pull mode, the code was already correct. When GstBaseSink operate in pull mode, the processing time is done durring the pull, so pull-post is the right hook. The synchronization will happen after the pull has ended. Note that GstBaseSink rarely operate in pull mode. https://bugzilla.gnome.org/show_bug.cgi?id=788431
247 lines
7.9 KiB
C
247 lines
7.9 KiB
C
/* GStreamer
|
|
* Copyright (C) 2013 Stefan Sauer <ensonic@users.sf.net>
|
|
*
|
|
* gstlatency.c: tracing module that logs processing latency stats
|
|
*
|
|
* 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.
|
|
*/
|
|
/**
|
|
* SECTION:gstlatency
|
|
* @short_description: log processing latency stats
|
|
*
|
|
* A tracing module that determines src-to-sink latencies by injecting custom
|
|
* events at sources and process them at sinks.
|
|
*/
|
|
/* TODO(ensonic): if there are two sources feeding into a mixer/muxer and later
|
|
* we fan-out with tee and have two sinks, each sink would get all two events,
|
|
* the later event would overwrite the former. Unfortunately when the buffer
|
|
* arrives on the sink we don't know to which event it correlates. Better would
|
|
* be to use the buffer meta in 1.0 instead of the event. Or we track a min/max
|
|
* latency.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "gstlatency.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_latency_debug);
|
|
#define GST_CAT_DEFAULT gst_latency_debug
|
|
|
|
#define _do_init \
|
|
GST_DEBUG_CATEGORY_INIT (gst_latency_debug, "latency", 0, "latency tracer");
|
|
#define gst_latency_tracer_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstLatencyTracer, gst_latency_tracer, GST_TYPE_TRACER,
|
|
_do_init);
|
|
|
|
static GQuark latency_probe_id;
|
|
static GQuark latency_probe_pad;
|
|
static GQuark latency_probe_ts;
|
|
|
|
static GstTracerRecord *tr_latency;
|
|
|
|
/* data helpers */
|
|
|
|
/*
|
|
* Get the element/bin owning the pad.
|
|
*
|
|
* in: a normal pad
|
|
* out: the element
|
|
*
|
|
* in: a proxy pad
|
|
* out: the element that contains the peer of the proxy
|
|
*
|
|
* in: a ghost pad
|
|
* out: the bin owning the ghostpad
|
|
*/
|
|
/* TODO(ensonic): gst_pad_get_parent_element() would not work here, should we
|
|
* add this as new api, e.g. gst_pad_find_parent_element();
|
|
*/
|
|
static GstElement *
|
|
get_real_pad_parent (GstPad * pad)
|
|
{
|
|
GstObject *parent;
|
|
|
|
if (!pad)
|
|
return NULL;
|
|
|
|
parent = GST_OBJECT_PARENT (pad);
|
|
|
|
/* if parent of pad is a ghost-pad, then pad is a proxy_pad */
|
|
if (parent && GST_IS_GHOST_PAD (parent)) {
|
|
pad = GST_PAD_CAST (parent);
|
|
parent = GST_OBJECT_PARENT (pad);
|
|
}
|
|
return GST_ELEMENT_CAST (parent);
|
|
}
|
|
|
|
/* hooks */
|
|
|
|
static void
|
|
log_latency (const GstStructure * data, GstPad * sink_pad, guint64 sink_ts)
|
|
{
|
|
GstPad *src_pad;
|
|
guint64 src_ts;
|
|
gchar *src, *sink;
|
|
|
|
gst_structure_id_get (data,
|
|
latency_probe_pad, GST_TYPE_PAD, &src_pad,
|
|
latency_probe_ts, G_TYPE_UINT64, &src_ts, NULL);
|
|
|
|
src = g_strdup_printf ("%s_%s", GST_DEBUG_PAD_NAME (src_pad));
|
|
sink = g_strdup_printf ("%s_%s", GST_DEBUG_PAD_NAME (sink_pad));
|
|
|
|
gst_tracer_record_log (tr_latency, src, sink,
|
|
GST_CLOCK_DIFF (src_ts, sink_ts));
|
|
g_free (src);
|
|
g_free (sink);
|
|
}
|
|
|
|
static void
|
|
send_latency_probe (GstElement * parent, GstPad * pad, guint64 ts)
|
|
{
|
|
if (parent && (!GST_IS_BIN (parent)) &&
|
|
GST_OBJECT_FLAG_IS_SET (parent, GST_ELEMENT_FLAG_SOURCE)) {
|
|
GstEvent *latency_probe = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
|
|
gst_structure_new_id (latency_probe_id,
|
|
latency_probe_pad, GST_TYPE_PAD, pad,
|
|
latency_probe_ts, G_TYPE_UINT64, ts,
|
|
NULL));
|
|
gst_pad_push_event (pad, latency_probe);
|
|
}
|
|
}
|
|
|
|
static void
|
|
calculate_latency (GstElement * parent, GstPad * pad, guint64 ts)
|
|
{
|
|
if (parent && (!GST_IS_BIN (parent)) &&
|
|
GST_OBJECT_FLAG_IS_SET (parent, GST_ELEMENT_FLAG_SINK)) {
|
|
GstEvent *ev = g_object_get_qdata ((GObject *) pad, latency_probe_id);
|
|
|
|
if (ev) {
|
|
g_object_set_qdata ((GObject *) pad, latency_probe_id, NULL);
|
|
log_latency (gst_event_get_structure (ev), pad, ts);
|
|
gst_event_unref (ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
do_push_buffer_pre (GstTracer * self, guint64 ts, GstPad * pad)
|
|
{
|
|
GstPad *peer_pad = GST_PAD_PEER (pad);
|
|
GstElement *parent = get_real_pad_parent (pad);
|
|
GstElement *peer_parent = get_real_pad_parent (peer_pad);
|
|
|
|
send_latency_probe (parent, pad, ts);
|
|
calculate_latency (peer_parent, peer_pad, ts);
|
|
}
|
|
|
|
static void
|
|
do_pull_range_pre (GstTracer * self, guint64 ts, GstPad * pad)
|
|
{
|
|
GstPad *peer_pad = GST_PAD_PEER (pad);
|
|
GstElement *parent = get_real_pad_parent (peer_pad);
|
|
|
|
send_latency_probe (parent, peer_pad, ts);
|
|
}
|
|
|
|
static void
|
|
do_pull_range_post (GstTracer * self, guint64 ts, GstPad * pad)
|
|
{
|
|
GstElement *parent = get_real_pad_parent (pad);
|
|
|
|
calculate_latency (parent, pad, ts);
|
|
}
|
|
|
|
static void
|
|
do_push_event_pre (GstTracer * self, guint64 ts, GstPad * pad, GstEvent * ev)
|
|
{
|
|
GstPad *peer_pad = GST_PAD_PEER (pad);
|
|
GstElement *parent = get_real_pad_parent (peer_pad);
|
|
|
|
if (parent && (!GST_IS_BIN (parent)) &&
|
|
GST_OBJECT_FLAG_IS_SET (parent, GST_ELEMENT_FLAG_SINK)) {
|
|
if (GST_EVENT_TYPE (ev) == GST_EVENT_CUSTOM_DOWNSTREAM) {
|
|
const GstStructure *data = gst_event_get_structure (ev);
|
|
|
|
if (gst_structure_get_name_id (data) == latency_probe_id) {
|
|
/* store event and calculate latency when the buffer that follows
|
|
* has been processed */
|
|
g_object_set_qdata ((GObject *) peer_pad, latency_probe_id,
|
|
gst_event_ref (ev));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* tracer class */
|
|
|
|
static void
|
|
gst_latency_tracer_class_init (GstLatencyTracerClass * klass)
|
|
{
|
|
latency_probe_id = g_quark_from_static_string ("latency_probe.id");
|
|
latency_probe_pad = g_quark_from_static_string ("latency_probe.pad");
|
|
latency_probe_ts = g_quark_from_static_string ("latency_probe.ts");
|
|
|
|
/* announce trace formats */
|
|
/* *INDENT-OFF* */
|
|
tr_latency = gst_tracer_record_new ("latency.class",
|
|
"src", GST_TYPE_STRUCTURE, gst_structure_new ("scope",
|
|
"type", G_TYPE_GTYPE, G_TYPE_STRING,
|
|
"related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PAD,
|
|
NULL),
|
|
"sink", GST_TYPE_STRUCTURE, gst_structure_new ("scope",
|
|
"type", G_TYPE_GTYPE, G_TYPE_STRING,
|
|
"related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PAD,
|
|
NULL),
|
|
"time", GST_TYPE_STRUCTURE, gst_structure_new ("value",
|
|
"type", G_TYPE_GTYPE, G_TYPE_UINT64,
|
|
"description", G_TYPE_STRING,
|
|
"time it took for the buffer to go from src to sink ns",
|
|
"min", G_TYPE_UINT64, G_GUINT64_CONSTANT (0),
|
|
"max", G_TYPE_UINT64, G_MAXUINT64,
|
|
NULL),
|
|
NULL);
|
|
/* *INDENT-ON* */
|
|
}
|
|
|
|
static void
|
|
gst_latency_tracer_init (GstLatencyTracer * self)
|
|
{
|
|
GstTracer *tracer = GST_TRACER (self);
|
|
|
|
/* in push mode, pre/post will be called before/after the peer chain
|
|
* function has been called. For this reaosn, we only use -pre to avoid
|
|
* accounting for the processing time of the peer element (the sink) */
|
|
gst_tracing_register_hook (tracer, "pad-push-pre",
|
|
G_CALLBACK (do_push_buffer_pre));
|
|
gst_tracing_register_hook (tracer, "pad-push-list-pre",
|
|
G_CALLBACK (do_push_buffer_pre));
|
|
|
|
/* while in pull mode, pre/post will happend before and after the upstream
|
|
* pull_range call is made, so it already only account for the upstream
|
|
* processing time. As a side effect, in pull mode, we can measure the
|
|
* source processing latency, while in push mode, we can't */
|
|
gst_tracing_register_hook (tracer, "pad-pull-range-pre",
|
|
G_CALLBACK (do_pull_range_pre));
|
|
gst_tracing_register_hook (tracer, "pad-pull-range-post",
|
|
G_CALLBACK (do_pull_range_post));
|
|
|
|
gst_tracing_register_hook (tracer, "pad-push-event-pre",
|
|
G_CALLBACK (do_push_event_pre));
|
|
}
|