Xpra-org / xpra

Persistent remote applications for X11; screen sharing for X11, MacOS and MSWindows.
https://xpra.org/
GNU General Public License v2.0
1.97k stars 169 forks source link

Latency/Speed graph is not accurate #2802

Open totaam opened 4 years ago

totaam commented 4 years ago

Issue migrated from trac ticket # 2802

component: client | priority: minor

2020-06-08 12:40:29: stdedos created the issue


As insinuated also in #2617#comment:22, the graphs may not be accurate.

See attachment.

"Xpra-Python3-x86_64_4.0-26306\xpra_cmd" attach ssh://user@ip/20 --ssh="plink -ssh -agent" --modal-windows=no --title="@title@ on @@/@server-display@" --opengl=no --bandwidth-limit=6Mbps

2020-06-08 14:39:33,686 Xpra GTK3 client version 4.0-26306 64-bit
2020-06-08 14:39:33,688  running on Microsoft Windows 10
2020-06-08 14:39:33,769 Warning: failed to import opencv:
2020-06-08 14:39:33,770  No module named 'cv2'
2020-06-08 14:39:33,771  webcam forwarding is disabled
2020-06-08 14:39:34,502 GStreamer version 1.16.2 for Python 3.8.2 64-bit
2020-06-08 14:39:34,954 keyboard layout code 0x409
2020-06-08 14:39:34,954 identified as 'United States - English' : us
2020-06-08 14:39:35,240  keyboard settings: layout=us
2020-06-08 14:39:35,242  desktop size is 4160x1440 with 1 screen:
2020-06-08 14:39:35,242   Default (1100x381 mm - DPI: 96x96) workarea: 4160x1400
2020-06-08 14:39:35,242     Generic PnP Monitor 1600x900 at 0x534 (309x174 mm - DPI: 131x131) workarea: 1600x860
2020-06-08 14:39:35,243     C32JG5x 2560x1440 at 1600x0 (697x392 mm - DPI: 93x93) workarea: 2560x1400
totaam commented 4 years ago

2020-06-08 12:40:46: stdedos uploaded file 2020-06-08_14-37-15.mp4 (815.9 KiB)

totaam commented 1 year ago

Found this excellent answer on the gstreamer-devel mailing list: measuring FPS of a source live stream (using identity, elements):

using buffer probes on specific elements:

def measure_elem_stats(self, elem: Gst.Element) -> None:
        """Measure bitrate, fps and latency of gstreamer element"""
        stats = GstPipeline.GstElementStats(elem)
        add_buffer_probe(elem, "src", self._calc_stats, stats)
        add_buffer_probe(elem, "sink", self._probe_in, stats)

def add_buffer_probe(
    elem: Gst.Element,
    pad_name: str,
    func,
    data: Optional[object] = None,
) -> bool:
    """Add a buffer probe to pad of element

    :param elem: element to probe
    :param pad_name: name of the pad to probe, normally 'sink' or 'src'
    :param func: callback function called when a buffer is received on the pad
    :param data: optional data to pass to probe function, defaults to None
    :return: True if the probe was sucessfully installed,
             False otherwise.
    """
    if not elem:
        return False
    pad = elem.get_static_pad(pad_name)
    if not pad:
        # print(f"Couldn't find {elem.name}::{pad_name}")
        return False

    pad.add_probe(Gst.PadProbeType.BUFFER, func, data)

    return True

def _calc_stats(
        self, pad: Gst.Pad, info: Gst.PadProbeInfo, stats: GstElementStats
    ) -> Gst.PadProbeReturn:
        """Callback of buffer probe for measuring bitrate, fps and latency"""
        buf = info.get_buffer()
        time = self.get_pipe_timestamp()
        diff = time - buf.pts  # total latency from src
        elapsed = time - stats.perf_sink.prev_time

        if not is_clock_valid(stats.perf_sink.prev_time):
            stats.perf_sink.prev_time = time
        elif is_clock_valid(time) and elapsed > Gst.SECOND:
            # frames per second
            factor_n = elapsed / Gst.MSECOND
            factor_d = Gst.SECOND / Gst.MSECOND

            # bytes per second
            bps = stats.perf_sink.bps * factor_d / factor_n
            stats.bitrate = bps / (1024 * 1024 / 8)  # Mbps

            stats.fps = stats.perf_sink.frame_count * factor_d / factor_n

            latency = stats.perf_sink.latency / stats.perf_sink.frame_count
            stats.elem_latency = latency - stats.src_latency

            self.__logger.debug(
                f"{stats.elem_name}: "
                f"bitrate={stats.bitrate} Mbps "
                f"fps={stats.fps} "
                f"latency elem={timestamp_msecs(stats.elem_latency)} ms "
                f"total={timestamp_msecs(latency)} ms"
            )
            self._plot_stats(time, stats)

            stats.perf_sink.reset(time)

        stats.perf_sink.frame_count += 1
        stats.perf_sink.bps += buf.get_size()
        stats.perf_sink.latency += diff

        return Gst.PadProbeReturn.OK

    def _probe_in(
        self, pad: Gst.Pad, info: Gst.PadProbeInfo, stats: GstElementStats
    ) -> Gst.PadProbeReturn:
        """Callback of buffer probe for measuring processing time"""
        buf = info.get_buffer()
        time = self.get_pipe_timestamp()
        diff = time - buf.pts  # latency from src
        elapsed = time - stats.perf_src.prev_time

        if not is_clock_valid(stats.perf_src.prev_time):
            stats.perf_src.prev_time = time
        elif is_clock_valid(time) and elapsed > Gst.SECOND:
            stats.src_latency = stats.perf_src.latency / stats.perf_src.frame_count

            # self.__logger.debug(
            #    f"{stats.elem_name}: src_latency={latency / Gst.MSECOND} ms"
            # )

            stats.perf_src.reset(time)

        stats.perf_src.frame_count += 1
        stats.perf_src.latency += diff

        return Gst.PadProbeReturn.OK

class GstElementStats:
        """Statistic values for Gst.Element

        :param elem: gstreamer element to measure statistics on
        """

        class PerfData:
            """Used for measuring bitrate, fps and latencies in buffer probes"""

            def __init__(self):
                """Constructor"""
                self.prev_time = Gst.CLOCK_TIME_NONE
                self.frame_count = 0
                self.bps = 0
                self.latency = 0

            def reset(self, time: int) -> None:
                """Reset values"""
                self.frame_count = 0
                self.bps = 0
                self.latency = 0
                self.prev_time = time

        def __init__(self, elem: Gst.Element):
            """Constructor"""
            self.bitrate = 0  # in Mbps
            self.fps = 0
            self.src_latency = 0  # in ms
            self.elem_latency = 0  # in ms
            self.elem_name = elem.name
            self.perf_src = GstPipeline.GstElementStats.PerfData()
            self.perf_sink = GstPipeline.GstElementStats.PerfData()