LCOV - code coverage report
Current view: top level - media/server/gstplayer/source - GstProfiler.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 67.8 % 199 135
Test Date: 2026-05-14 05:59:51 Functions: 91.3 % 23 21

            Line data    Source code
       1              : /*
       2              :  * If not stated otherwise in this file or this component's LICENSE file the
       3              :  * following copyright and licenses apply:
       4              :  *
       5              :  * Copyright 2026 Sky UK
       6              :  *
       7              :  * Licensed under the Apache License, Version 2.0 (the "License");
       8              :  * you may not use this file except in compliance with the License.
       9              :  * You may obtain a copy of the License at
      10              :  *
      11              :  * http://www.apache.org/licenses/LICENSE-2.0
      12              :  *
      13              :  * Unless required by applicable law or agreed to in writing, software
      14              :  * distributed under the License is distributed on an "AS IS" BASIS,
      15              :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      16              :  * See the License for the specific language governing permissions and
      17              :  * limitations under the License.
      18              :  */
      19              : 
      20              : #include "GstProfiler.h"
      21              : #include "RialtoServerLogging.h"
      22              : #include "Utils.h"
      23              : 
      24              : #include <algorithm>
      25              : #include <array>
      26              : #include <cctype>
      27              : #include <string>
      28              : #include <string_view>
      29              : 
      30              : #include <glib.h>
      31              : #include <gst/gst.h>
      32              : 
      33              : namespace firebolt::rialto::server
      34              : {
      35              : std::weak_ptr<IGstProfilerFactory> GstProfilerFactory::m_factory;
      36              : 
      37            1 : std::shared_ptr<IGstProfilerFactory> IGstProfilerFactory::getFactory()
      38              : {
      39            1 :     std::shared_ptr<IGstProfilerFactory> factory = GstProfilerFactory::m_factory.lock();
      40              : 
      41            1 :     if (!factory)
      42              :     {
      43              :         try
      44              :         {
      45            1 :             factory = std::make_shared<GstProfilerFactory>();
      46              :         }
      47            0 :         catch (const std::exception &e)
      48              :         {
      49            0 :             RIALTO_SERVER_LOG_ERROR("Failed to create the gst profiler factory, reason: %s", e.what());
      50              :         }
      51              : 
      52            1 :         GstProfilerFactory::m_factory = factory;
      53              :     }
      54              : 
      55            1 :     return factory;
      56              : }
      57              : 
      58            1 : std::unique_ptr<IGstProfiler> GstProfilerFactory::createGstProfiler(GstElement *pipeline,
      59              :                                                                     const std::shared_ptr<IGstWrapper> &gstWrapper,
      60              :                                                                     const std::shared_ptr<IGlibWrapper> &glibWrapper) const
      61              : {
      62            1 :     return std::make_unique<GstProfiler>(pipeline, gstWrapper, glibWrapper);
      63              : }
      64              : 
      65              : namespace
      66              : {
      67              : inline constexpr std::array kKlassTokens{
      68              :     std::string_view{"Source"},
      69              :     std::string_view{"Decryptor"},
      70              :     std::string_view{"Decoder"},
      71              : };
      72              : inline constexpr std::string_view kModuleName{"GstProfiler"};
      73              : 
      74              : using Clock = std::chrono::system_clock;
      75              : using IProfiler = firebolt::rialto::common::IProfiler;
      76              : 
      77            3 : GstPadProbeReturn probeCb(GstPad *pad, GstPadProbeInfo *info, gpointer userData)
      78              : {
      79            3 :     firebolt::rialto::server::IGstProfilerPrivate *self =
      80              :         static_cast<firebolt::rialto::server::IGstProfilerPrivate *>(userData);
      81            3 :     return self->handleProbeCb(pad, info);
      82              : }
      83              : 
      84            0 : std::optional<int64_t> diffMs(const std::optional<Clock::time_point> &end, const std::optional<Clock::time_point> &start)
      85              : {
      86            0 :     if (!end || !start)
      87            0 :         return std::nullopt;
      88              : 
      89            0 :     return std::chrono::duration_cast<std::chrono::milliseconds>(*end - *start).count();
      90              : }
      91              : 
      92            0 : std::optional<Clock::time_point> maxTime(const std::optional<Clock::time_point> &a,
      93              :                                          const std::optional<Clock::time_point> &b)
      94              : {
      95            0 :     if (a && b)
      96            0 :         return std::max(*a, *b);
      97            0 :     if (a)
      98            0 :         return a;
      99            0 :     if (b)
     100            0 :         return b;
     101            0 :     return std::nullopt;
     102              : }
     103              : } // namespace
     104              : 
     105           20 : GstProfiler::GstProfiler(GstElement *pipeline, const std::shared_ptr<firebolt::rialto::wrappers::IGstWrapper> &gstWrapper,
     106           20 :                          const std::shared_ptr<firebolt::rialto::wrappers::IGlibWrapper> &glibWrapper)
     107           20 :     : m_pipeline{pipeline}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}
     108              : {
     109           20 :     auto profilerFactory = firebolt::rialto::common::IProfilerFactory::createFactory();
     110           60 :     m_profiler = profilerFactory ? std::shared_ptr<IProfiler>{profilerFactory->createProfiler(std::string{kModuleName})}
     111           20 :                                  : nullptr;
     112           20 :     m_enabled = (m_profiler != nullptr) && m_profiler->isEnabled();
     113              : 
     114           20 :     if (m_enabled && m_pipeline)
     115            3 :         m_gstWrapper->gstObjectRef(m_pipeline);
     116           20 : }
     117              : 
     118           40 : GstProfiler::~GstProfiler()
     119              : {
     120           21 :     while (!m_probeCtxs.empty())
     121              :     {
     122            1 :         auto &probeCtx = m_probeCtxs.back();
     123            1 :         if (probeCtx.id != 0)
     124              :         {
     125            1 :             m_gstWrapper->gstPadRemoveProbe(probeCtx.pad, probeCtx.id);
     126              :         }
     127            1 :         m_gstWrapper->gstObjectUnref(probeCtx.pad);
     128            1 :         m_probeCtxs.pop_back();
     129              :     }
     130              : 
     131           20 :     if (m_enabled && m_pipeline)
     132            3 :         m_gstWrapper->gstObjectUnref(m_pipeline);
     133           40 : }
     134              : 
     135           11 : std::optional<GstProfiler::RecordId> GstProfiler::createRecord(const std::string &stage)
     136              : {
     137           11 :     if (!m_enabled || !m_profiler)
     138            1 :         return std::nullopt;
     139              : 
     140           10 :     auto id = m_profiler->record(stage);
     141           10 :     if (!id)
     142            0 :         return std::nullopt;
     143              : 
     144           10 :     return static_cast<GstProfiler::RecordId>(*id);
     145              : }
     146              : 
     147            2 : std::optional<GstProfiler::RecordId> GstProfiler::createRecord(const std::string &stage, const std::string &info)
     148              : {
     149            2 :     if (!m_enabled || !m_profiler)
     150            0 :         return std::nullopt;
     151              : 
     152            2 :     auto id = m_profiler->record(stage, info);
     153            2 :     if (!id)
     154            0 :         return std::nullopt;
     155              : 
     156            2 :     return static_cast<GstProfiler::RecordId>(*id);
     157              : }
     158              : 
     159           10 : void GstProfiler::scheduleGstElementRecord(GstElement *element)
     160              : {
     161           10 :     if (!m_enabled || !m_profiler)
     162            6 :         return;
     163              : 
     164            9 :     if (!element)
     165            1 :         return;
     166              : 
     167            8 :     auto stage = getFirstBufferExitStage(element);
     168            8 :     if (!stage)
     169            0 :         return;
     170              : 
     171            8 :     GstPad *pad = m_gstWrapper->gstElementGetStaticPad(element, "src");
     172            8 :     if (!pad)
     173            3 :         return;
     174              : 
     175            5 :     std::string elementInfo;
     176            5 :     if (isVideo(*m_gstWrapper, element))
     177              :     {
     178            1 :         elementInfo = "Video";
     179              :     }
     180            4 :     else if (isAudio(*m_gstWrapper, element))
     181              :     {
     182            0 :         elementInfo = "Audio";
     183              :     }
     184              :     else
     185              :     {
     186            4 :         gchar *rawName = m_gstWrapper->gstElementGetName(element);
     187            4 :         elementInfo = deriveElementInfoFromName(rawName ? rawName : "<null>");
     188            4 :         if (rawName)
     189            4 :             m_glibWrapper->gFree(rawName);
     190              :     }
     191              : 
     192            5 :     const auto probeId = m_gstWrapper->gstPadAddProbe(pad, GST_PAD_PROBE_TYPE_BUFFER, &probeCb,
     193              :                                                       static_cast<IGstProfilerPrivate *>(this), nullptr);
     194            5 :     if (probeId == 0)
     195              :     {
     196            1 :         m_gstWrapper->gstObjectUnref(pad);
     197            1 :         return;
     198              :     }
     199              : 
     200            4 :     m_probeCtxs.emplace_back(ProbeCtx{m_profiler, stage.value(), std::move(elementInfo), pad, probeId});
     201            9 : }
     202              : 
     203            3 : std::vector<IGstProfiler::Record> GstProfiler::getRecords() const
     204              : {
     205            3 :     if (!m_profiler)
     206            0 :         return {};
     207              : 
     208            3 :     return m_profiler->getRecords();
     209              : }
     210              : 
     211            4 : void GstProfiler::logRecord(GstProfiler::RecordId id)
     212              : {
     213            4 :     if (!m_enabled || !m_profiler)
     214            0 :         return;
     215              : 
     216            4 :     m_profiler->log(static_cast<firebolt::rialto::common::IProfiler::RecordId>(id));
     217              : }
     218              : 
     219            4 : void GstProfiler::dumpToFile() const
     220              : {
     221            4 :     if (!m_enabled || !m_profiler)
     222            0 :         return;
     223              : 
     224            4 :     if (!m_profiler->dumpToFile())
     225              :     {
     226            1 :         RIALTO_SERVER_LOG_WARN("Failed to dump profiler records to file");
     227              :     }
     228              : }
     229              : 
     230            2 : void GstProfiler::logPipelineSummary() const
     231              : {
     232            2 :     if (!m_enabled || !m_profiler)
     233            0 :         return;
     234              : 
     235            2 :     const auto metrics = calculateMetrics();
     236            2 :     if (metrics)
     237              :     {
     238            0 :         RIALTO_SERVER_LOG_MIL("PROFILER | TUNETIME: %lld,  %lld,  %lld,  %lld,  %lld,  %lld,  %lld,  %lld,  %lld,  "
     239              :                               "%lld,  %lld,  %lld,  %lld",
     240              :                               metrics->preparation ? static_cast<long long>(*metrics->preparation) : -1,     // NOLINT
     241              :                               metrics->videoDownload ? static_cast<long long>(*metrics->videoDownload) : -1, // NOLINT
     242              :                               metrics->audioDownload ? static_cast<long long>(*metrics->audioDownload) : -1, // NOLINT
     243              :                               metrics->videoSource ? static_cast<long long>(*metrics->videoSource) : -1,     // NOLINT
     244              :                               metrics->audioSource ? static_cast<long long>(*metrics->audioSource) : -1,     // NOLINT
     245              :                               metrics->videoDecryption ? static_cast<long long>(*metrics->videoDecryption) : -1, // NOLINT
     246              :                               metrics->audioDecryption ? static_cast<long long>(*metrics->audioDecryption) : -1, // NOLINT
     247              :                               metrics->videoDecode ? static_cast<long long>(*metrics->videoDecode) : -1, // NOLINT
     248              :                               metrics->audioDecode ? static_cast<long long>(*metrics->audioDecode) : -1, // NOLINT
     249              :                               metrics->preRoll ? static_cast<long long>(*metrics->preRoll) : -1,         // NOLINT
     250              :                               metrics->play ? static_cast<long long>(*metrics->play) : -1,               // NOLINT
     251              :                               metrics->total ? static_cast<long long>(*metrics->total) : -1,             // NOLINT
     252              :                               metrics->totalWithoutApp ? static_cast<long long>(*metrics->totalWithoutApp) : -1); // NOLINT
     253              :     }
     254              : }
     255              : 
     256            3 : GstPadProbeReturn GstProfiler::handleProbeCb(GstPad *pad, GstPadProbeInfo *info)
     257              : {
     258            3 :     if (!(info->type & GST_PAD_PROBE_TYPE_BUFFER))
     259            0 :         return GST_PAD_PROBE_OK;
     260              : 
     261            3 :     if (!GST_PAD_PROBE_INFO_BUFFER(info))
     262            0 :         return GST_PAD_PROBE_OK;
     263              : 
     264              :     const auto probeCtx =
     265            6 :         std::find_if(m_probeCtxs.begin(), m_probeCtxs.end(), [pad](const auto &ctx) { return ctx.pad == pad; });
     266            3 :     if (probeCtx == m_probeCtxs.end())
     267            0 :         return GST_PAD_PROBE_REMOVE;
     268              : 
     269            3 :     if (probeCtx->profiler)
     270              :     {
     271            3 :         const auto id = probeCtx->profiler->record(probeCtx->stage, probeCtx->info);
     272            3 :         if (id)
     273              :         {
     274            3 :             probeCtx->profiler->log(id.value());
     275              :         }
     276              :     }
     277              : 
     278            3 :     removeProbeCtx(pad);
     279            3 :     return GST_PAD_PROBE_REMOVE;
     280              : }
     281              : 
     282            8 : std::optional<std::string> GstProfiler::getFirstBufferExitStage(GstElement *element)
     283              : {
     284            8 :     const gchar *klass = getElementClassMetadata(element);
     285            8 :     if (!klass)
     286            0 :         return std::nullopt;
     287              : 
     288            8 :     for (auto token : kKlassTokens)
     289              :     {
     290            8 :         if (m_glibWrapper->gStrrstr(klass, token.data()) != nullptr)
     291              :         {
     292           16 :             return std::string(token.data()) + " FB Exit";
     293              :         }
     294              :     }
     295              : 
     296            0 :     return std::nullopt;
     297              : }
     298              : 
     299            8 : const gchar *GstProfiler::getElementClassMetadata(GstElement *element)
     300              : {
     301            8 :     return m_gstWrapper->gstElementClassGetMetadata(GST_ELEMENT_CLASS(G_OBJECT_GET_CLASS(element)),
     302            8 :                                                     GST_ELEMENT_METADATA_KLASS);
     303              : }
     304              : 
     305            4 : std::string GstProfiler::deriveElementInfoFromName(const std::string &name) const
     306              : {
     307            4 :     std::string lower = name;
     308           54 :     std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); });
     309              : 
     310            4 :     if (lower.find("vid") != std::string::npos || lower.find("video") != std::string::npos)
     311              :     {
     312            6 :         return "Video";
     313              :     }
     314              : 
     315            1 :     if (lower.find("aud") != std::string::npos || lower.find("audio") != std::string::npos)
     316              :     {
     317            0 :         return "Audio";
     318              :     }
     319              : 
     320            1 :     return name;
     321            4 : }
     322              : 
     323            3 : void GstProfiler::removeProbeCtx(GstPad *pad)
     324              : {
     325              :     const auto probeCtx =
     326            6 :         std::find_if(m_probeCtxs.begin(), m_probeCtxs.end(), [pad](const auto &ctx) { return ctx.pad == pad; });
     327            3 :     if (probeCtx == m_probeCtxs.end())
     328            0 :         return;
     329              : 
     330            3 :     m_gstWrapper->gstObjectUnref(probeCtx->pad);
     331            3 :     m_probeCtxs.erase(probeCtx);
     332              : }
     333              : 
     334            2 : std::optional<GstProfiler::PipelineMetrics> GstProfiler::calculateMetrics() const
     335              : {
     336            2 :     const auto records = m_profiler->getRecords();
     337              : 
     338            2 :     PipelineStageTimestamps timestamps;
     339              : 
     340            4 :     for (const auto &record : records)
     341              :     {
     342            2 :         const auto &stage = record.stage;
     343            2 :         const auto &info = record.info;
     344              : 
     345            2 :         if (!timestamps.pipelineCreated && stage == "Pipeline Created")
     346            0 :             timestamps.pipelineCreated = record.time;
     347            2 :         else if (!timestamps.allSourcesAttached && stage == "All Sources Attached")
     348            0 :             timestamps.allSourcesAttached = record.time;
     349            2 :         else if (!timestamps.firstSegmentReceivedVideo && stage == "First Segment Received" && info == "Video")
     350            0 :             timestamps.firstSegmentReceivedVideo = record.time;
     351            2 :         else if (!timestamps.firstSegmentReceivedAudio && stage == "First Segment Received" && info == "Audio")
     352            0 :             timestamps.firstSegmentReceivedAudio = record.time;
     353            2 :         else if (!timestamps.sourceFbExitVideo && stage == "Source FB Exit" && info == "Video")
     354            0 :             timestamps.sourceFbExitVideo = record.time;
     355            2 :         else if (!timestamps.sourceFbExitAudio && stage == "Source FB Exit" && info == "Audio")
     356            0 :             timestamps.sourceFbExitAudio = record.time;
     357            2 :         else if (!timestamps.decryptorFbExitVideo && stage == "Decryptor FB Exit" && info == "Video")
     358            0 :             timestamps.decryptorFbExitVideo = record.time;
     359            2 :         else if (!timestamps.decryptorFbExitAudio && stage == "Decryptor FB Exit" && info == "Audio")
     360            0 :             timestamps.decryptorFbExitAudio = record.time;
     361            2 :         else if (!timestamps.decoderFbExitVideo && stage == "Decoder FB Exit" && info == "Video")
     362            0 :             timestamps.decoderFbExitVideo = record.time;
     363            2 :         else if (!timestamps.decoderFbExitAudio && stage == "Decoder FB Exit" && info == "Audio")
     364            0 :             timestamps.decoderFbExitAudio = record.time;
     365            2 :         else if (!timestamps.pipelinePaused && stage == "Pipeline State Changed" && info == "PAUSED")
     366            0 :             timestamps.pipelinePaused = record.time;
     367            2 :         else if (!timestamps.pipelinePlaying && stage == "Pipeline State Changed" && info == "PLAYING")
     368            0 :             timestamps.pipelinePlaying = record.time;
     369              :     }
     370              : 
     371            2 :     if (!timestamps.pipelineCreated || !timestamps.allSourcesAttached || !timestamps.firstSegmentReceivedVideo ||
     372            0 :         !timestamps.firstSegmentReceivedAudio || !timestamps.sourceFbExitVideo || !timestamps.sourceFbExitAudio ||
     373            2 :         !timestamps.decoderFbExitVideo || !timestamps.decoderFbExitAudio || !timestamps.pipelinePaused ||
     374            0 :         !timestamps.pipelinePlaying)
     375              :     {
     376            2 :         return std::nullopt;
     377              :     }
     378              : 
     379            0 :     PipelineMetrics metrics;
     380              : 
     381            0 :     metrics.preparation = diffMs(timestamps.allSourcesAttached, timestamps.pipelineCreated);
     382            0 :     metrics.videoDownload = diffMs(timestamps.firstSegmentReceivedVideo, timestamps.allSourcesAttached);
     383            0 :     metrics.audioDownload = diffMs(timestamps.firstSegmentReceivedAudio, timestamps.allSourcesAttached);
     384            0 :     metrics.videoSource = diffMs(timestamps.sourceFbExitVideo, timestamps.firstSegmentReceivedVideo);
     385            0 :     metrics.audioSource = diffMs(timestamps.sourceFbExitAudio, timestamps.firstSegmentReceivedAudio);
     386              : 
     387            0 :     if (timestamps.decryptorFbExitVideo && timestamps.decryptorFbExitAudio)
     388              :     {
     389            0 :         metrics.videoDecryption = diffMs(timestamps.decryptorFbExitVideo, timestamps.sourceFbExitVideo);
     390            0 :         metrics.audioDecryption = diffMs(timestamps.decryptorFbExitAudio, timestamps.sourceFbExitAudio);
     391            0 :         metrics.videoDecode = diffMs(timestamps.decoderFbExitVideo, timestamps.decryptorFbExitVideo);
     392            0 :         metrics.audioDecode = diffMs(timestamps.decoderFbExitAudio, timestamps.decryptorFbExitAudio);
     393              :     }
     394              :     else
     395              :     {
     396            0 :         metrics.videoDecode = diffMs(timestamps.decoderFbExitVideo, timestamps.sourceFbExitVideo);
     397            0 :         metrics.audioDecode = diffMs(timestamps.decoderFbExitAudio, timestamps.sourceFbExitAudio);
     398              :     }
     399              : 
     400            0 :     const auto firstMediaReady = maxTime(timestamps.firstSegmentReceivedVideo, timestamps.firstSegmentReceivedAudio);
     401              : 
     402            0 :     metrics.preRoll = diffMs(timestamps.pipelinePaused, firstMediaReady);
     403            0 :     metrics.play = diffMs(timestamps.pipelinePlaying, timestamps.pipelinePaused);
     404            0 :     metrics.total = diffMs(timestamps.pipelinePlaying, timestamps.pipelineCreated);
     405            0 :     metrics.totalWithoutApp = diffMs(timestamps.pipelinePlaying, firstMediaReady);
     406              : 
     407            0 :     return metrics;
     408            2 : }
     409              : 
     410              : } // namespace firebolt::rialto::server
        

Generated by: LCOV version 2.0-1