LCOV - code coverage report
Current view: top level - media/server/main/source - MetricsCollector.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 87.0 % 284 247
Test Date: 2026-09-11 17:36:12 Functions: 94.7 % 19 18

            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 "MetricsCollector.h"
      21              : #include "LogMetricsReporter.h"
      22              : #include "RialtoServerLogging.h"
      23              : #include <chrono>
      24              : #include <cinttypes>
      25              : #include <fstream>
      26              : #include <string>
      27              : #include <sys/times.h>
      28              : #include <unistd.h>
      29              : 
      30              : namespace
      31              : {
      32              : constexpr std::chrono::seconds kMetricsInterval{15};
      33              : constexpr std::uint64_t kMinElapsedMs{100};
      34              : constexpr unsigned int kResponseTimeoutTimerCount{2};
      35              : } // namespace
      36              : 
      37              : namespace firebolt::rialto::server
      38              : {
      39            7 : std::shared_ptr<IMetricsCollectorFactory> IMetricsCollectorFactory::createFactory()
      40              : {
      41            7 :     std::shared_ptr<IMetricsCollectorFactory> factory;
      42              :     try
      43              :     {
      44            7 :         factory = std::make_shared<MetricsCollectorFactory>();
      45              :     }
      46            0 :     catch (const std::exception &e)
      47              :     {
      48            0 :         RIALTO_SERVER_LOG_ERROR("Failed to create MetricsCollectorFactory, reason: %s", e.what());
      49              :     }
      50            7 :     return factory;
      51              : }
      52              : 
      53            0 : std::unique_ptr<IMetricsCollector> MetricsCollectorFactory::create(int clientId,
      54              :                                                                    const std::shared_ptr<IMetricsCollectorClient> &client,
      55              :                                                                    ApplicationState initialApplicationState)
      56              : {
      57            0 :     std::unique_ptr<IMetricsCollector> collector;
      58              :     try
      59              :     {
      60            0 :         auto timerFactory = firebolt::rialto::common::ITimerFactory::getFactory();
      61            0 :         collector = std::make_unique<MetricsCollector>(clientId, client, timerFactory, initialApplicationState);
      62              :     }
      63            0 :     catch (const std::exception &e)
      64              :     {
      65            0 :         RIALTO_SERVER_LOG_ERROR("Failed to create MetricsCollector for client %d, reason: %s", clientId, e.what());
      66              :     }
      67            0 :     return collector;
      68              : }
      69              : 
      70            3 : MetricsCollector::MetricsCollector(int clientId, const std::shared_ptr<IMetricsCollectorClient> &client,
      71              :                                    const std::shared_ptr<firebolt::rialto::common::ITimerFactory> &timerFactory,
      72            3 :                                    ApplicationState initialApplicationState)
      73            6 :     : m_clientId{clientId}, m_client{client}, m_currentApplicationState{initialApplicationState},
      74           21 :       m_reporter{std::make_unique<LogMetricsReporter>()}, m_thresholdConfig{},
      75            6 :       m_thresholdChecker{m_thresholdConfig, m_reporter.get()}
      76              : {
      77            3 :     if (m_currentApplicationState == ApplicationState::RUNNING)
      78              :     {
      79              :         using std::chrono::duration_cast;
      80              :         using std::chrono::milliseconds;
      81              :         using std::chrono::steady_clock;
      82              :         const auto kNowMs{
      83            1 :             static_cast<std::uint64_t>(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count())};
      84            3 :         m_globalAggregator.begin(applicationStateToString(m_currentApplicationState), kNowMs);
      85              :     }
      86              : 
      87              :     m_timer =
      88              :         timerFactory
      89            7 :             ->createTimer(kMetricsInterval, [this]() { onTimerFired(); }, firebolt::rialto::common::TimerType::PERIODIC);
      90              : 
      91              :     // Request initial baseline sample
      92            3 :     m_client->requestMetricsSample(m_clientId, m_nextSampleId++, MetricsSampleReason::CONNECTED);
      93              : }
      94              : 
      95            6 : MetricsCollector::~MetricsCollector()
      96              : {
      97            3 :     if (m_timer)
      98              :     {
      99            3 :         m_timer->cancel();
     100              :     }
     101            6 : }
     102              : 
     103            4 : void MetricsCollector::onTimerFired()
     104              : {
     105            4 :     std::uint64_t sampleId{0};
     106              : 
     107              :     {
     108            4 :         std::lock_guard<std::mutex> lock{m_mutex};
     109            4 :         if (m_pendingPeriodicSampleId && ++m_pendingPeriodicTimerCount < kResponseTimeoutTimerCount)
     110              :         {
     111            1 :             return;
     112              :         }
     113              : 
     114            3 :         if (m_pendingPeriodicSampleId && m_clientResponsive)
     115              :         {
     116            1 :             m_clientResponsive = false;
     117            1 :             RIALTO_SERVER_LOG_WARN("Metrics client %d is not responding to sample requests", m_clientId);
     118              :         }
     119              : 
     120            3 :         sampleId = m_nextSampleId++;
     121            3 :         m_pendingPeriodicSampleId = sampleId;
     122            3 :         m_pendingPeriodicTimerCount = 0;
     123            4 :     }
     124              : 
     125            3 :     RIALTO_SERVER_LOG_DEBUG("Requesting periodic metrics sample=%" PRIu64 " from client %d", sampleId, m_clientId);
     126            3 :     m_client->requestMetricsSample(m_clientId, sampleId, MetricsSampleReason::PERIODIC);
     127              : }
     128              : 
     129            3 : void MetricsCollector::processMetrics(const ClientMetricsData &metrics)
     130              : {
     131            3 :     const auto kServerMetrics{getServerMetrics()};
     132              : 
     133            3 :     std::optional<PreviousSample> previous;
     134            3 :     ApplicationState applicationState{ApplicationState::UNKNOWN};
     135            3 :     bool becameResponsive{false};
     136              :     {
     137            3 :         std::lock_guard<std::mutex> lock{m_mutex};
     138            3 :         previous = m_previousSample;
     139            3 :         applicationState = m_currentApplicationState;
     140            3 :         if (metrics.reason == MetricsSampleReason::PERIODIC)
     141              :         {
     142            2 :             if (m_pendingPeriodicSampleId && metrics.sampleId == *m_pendingPeriodicSampleId)
     143              :             {
     144            1 :                 m_pendingPeriodicSampleId.reset();
     145            1 :                 m_pendingPeriodicTimerCount = 0;
     146            1 :                 if (!m_clientResponsive)
     147              :                 {
     148            1 :                     m_clientResponsive = true;
     149            1 :                     becameResponsive = true;
     150              :                 }
     151              :             }
     152              :         }
     153            3 :     }
     154              : 
     155            3 :     if (becameResponsive)
     156              :     {
     157            1 :         RIALTO_SERVER_LOG_INFO("Metrics client %d is responding again", m_clientId);
     158              :     }
     159              : 
     160            3 :     if (!previous.has_value())
     161              :     {
     162              :         // Baseline sample — store and return
     163            2 :         RIALTO_SERVER_LOG_MIL("Metrics baseline: sample=%" PRIu64 ", reason=%s, app='%s', client_pid=%u, "
     164              :                               "client_cpu_ms=%" PRIu64 ", server_cpu_ms=%" PRIu64 ", "
     165              :                               "client_mem_kb=%" PRIu64 ", server_mem_kb=%" PRIu64 ", "
     166              :                               "cgroup_mem_kb=%" PRIu64 "/%" PRIu64,
     167              :                               metrics.sampleId, sampleReasonToString(metrics.reason), metrics.appName.c_str(),
     168              :                               metrics.processId, metrics.processCpuTimeMs, kServerMetrics.processCpuTimeMs,
     169              :                               metrics.processMemoryKb, kServerMetrics.processMemoryKb,
     170              :                               kServerMetrics.cgroupMemoryUsageKb, kServerMetrics.cgroupMemoryLimitKb);
     171              : 
     172            2 :         std::lock_guard<std::mutex> lock{m_mutex};
     173              :         m_previousSample =
     174            2 :             PreviousSample{metrics.monotonicTimeMs, metrics.processCpuTimeMs, metrics.processMemoryKb, kServerMetrics};
     175            2 :         return;
     176              :     }
     177              : 
     178            1 :     const auto &prev{previous.value()};
     179            1 :     const double kClientCpuPercentage{calculateCpuPercentage(metrics.processCpuTimeMs, prev.clientCpuTimeMs,
     180            1 :                                                              metrics.monotonicTimeMs, prev.clientMonotonicTimeMs)};
     181              :     const double kServerCpuPercentage{
     182            1 :         calculateCpuPercentage(kServerMetrics.processCpuTimeMs, prev.serverMetrics.processCpuTimeMs,
     183            1 :                                kServerMetrics.monotonicTimeMs, prev.serverMetrics.monotonicTimeMs)};
     184              :     const double kCombinedCpuPercentage{
     185            1 :         calculateCpuPercentage(metrics.processCpuTimeMs + kServerMetrics.processCpuTimeMs,
     186            1 :                                prev.clientCpuTimeMs + prev.serverMetrics.processCpuTimeMs,
     187            1 :                                kServerMetrics.monotonicTimeMs, prev.serverMetrics.monotonicTimeMs)};
     188              : 
     189              :     // Report via pluggable reporter
     190            1 :     if (m_reporter)
     191              :     {
     192            1 :         PeriodicMetricsReport periodicReport;
     193            1 :         periodicReport.sampleId = metrics.sampleId;
     194            1 :         periodicReport.monotonicTimeMs = kServerMetrics.monotonicTimeMs;
     195            1 :         periodicReport.reason = sampleReasonToString(metrics.reason);
     196            1 :         periodicReport.applicationState = applicationState;
     197            1 :         periodicReport.appName = metrics.appName;
     198            1 :         periodicReport.clientPid = metrics.processId;
     199            1 :         periodicReport.clientCpuPercent = kClientCpuPercentage;
     200            1 :         periodicReport.serverCpuPercent = kServerCpuPercentage;
     201            1 :         periodicReport.combinedCpuPercent = kCombinedCpuPercentage;
     202            1 :         periodicReport.clientCpuTimeMs = metrics.processCpuTimeMs;
     203            1 :         periodicReport.serverCpuTimeMs = kServerMetrics.processCpuTimeMs;
     204            1 :         periodicReport.clientMemoryKb = metrics.processMemoryKb;
     205            1 :         periodicReport.serverMemoryKb = kServerMetrics.processMemoryKb;
     206            1 :         periodicReport.cgroupMemoryUsageKb = kServerMetrics.cgroupMemoryUsageKb;
     207            1 :         periodicReport.cgroupMemoryLimitKb = kServerMetrics.cgroupMemoryLimitKb;
     208            1 :         periodicReport.shmMemoryKb = kServerMetrics.shmMemoryKb;
     209            1 :         m_reporter->reportPeriodicSample(periodicReport);
     210              :     }
     211              : 
     212              :     // Only feed PERIODIC samples into aggregators — STATE_TRANSITION samples have
     213              :     // unreliable CPU percentages due to tiny time deltas between rapid samples.
     214            1 :     if (metrics.reason == MetricsSampleReason::PERIODIC)
     215              :     {
     216            1 :         MetricsSample sample;
     217            1 :         sample.clientCpuPercent = kClientCpuPercentage;
     218            1 :         sample.serverCpuPercent = kServerCpuPercentage;
     219            1 :         sample.combinedCpuPercent = kCombinedCpuPercentage;
     220            1 :         sample.clientMemoryKb = metrics.processMemoryKb;
     221            1 :         sample.serverMemoryKb = kServerMetrics.processMemoryKb;
     222            1 :         sample.cgroupMemoryUsageKb = kServerMetrics.cgroupMemoryUsageKb;
     223            1 :         sample.cgroupMemoryLimitKb = kServerMetrics.cgroupMemoryLimitKb;
     224              : 
     225              :         {
     226            1 :             std::lock_guard<std::mutex> lock{m_mutex};
     227              : 
     228              :             // Feed into per-session aggregators
     229            3 :             for (auto &[unusedContext, sessionState] : m_sessionStates)
     230              :             {
     231              :                 (void)unusedContext;
     232            2 :                 sessionState.aggregator.addSample(sample);
     233              :             }
     234              : 
     235              :             // Feed into global aggregator
     236            1 :             if (m_currentApplicationState == ApplicationState::RUNNING)
     237              :             {
     238            1 :                 m_globalAggregator.addSample(sample);
     239              :             }
     240              :         }
     241              : 
     242              :         // Check thresholds
     243            1 :         m_thresholdChecker.checkSample(kClientCpuPercentage, kServerCpuPercentage, kCombinedCpuPercentage,
     244            1 :                                        metrics.processMemoryKb, kServerMetrics.processMemoryKb,
     245            1 :                                        kServerMetrics.cgroupMemoryUsageKb, kServerMetrics.cgroupMemoryLimitKb);
     246              :     }
     247              : 
     248              :     // Update previous sample
     249              :     {
     250            1 :         std::lock_guard<std::mutex> lock{m_mutex};
     251              :         m_previousSample =
     252            1 :             PreviousSample{metrics.monotonicTimeMs, metrics.processCpuTimeMs, metrics.processMemoryKb, kServerMetrics};
     253              :     }
     254              : }
     255              : 
     256            3 : void MetricsCollector::notifyPlaybackStateChanged(int sessionId, PlaybackState oldState, PlaybackState newState)
     257              : {
     258            3 :     notifyPlayerStateChanged("media-pipeline=" + std::to_string(sessionId), playbackStateToString(oldState),
     259              :                              playbackStateToString(newState),
     260            3 :                              newState == PlaybackState::STOPPED || newState == PlaybackState::END_OF_STREAM ||
     261              :                                  newState == PlaybackState::FAILURE);
     262              : }
     263              : 
     264            3 : void MetricsCollector::notifyWebAudioPlayerStateChanged(int handle, WebAudioPlayerState oldState,
     265              :                                                         WebAudioPlayerState newState)
     266              : {
     267            3 :     notifyPlayerStateChanged("web-audio=" + std::to_string(handle), webAudioPlayerStateToString(oldState),
     268              :                              webAudioPlayerStateToString(newState),
     269            3 :                              newState == WebAudioPlayerState::END_OF_STREAM || newState == WebAudioPlayerState::FAILURE);
     270              : }
     271              : 
     272            6 : void MetricsCollector::notifyPlayerStateChanged(const std::string &context, const char *oldState, const char *newState,
     273              :                                                 bool terminalState)
     274              : {
     275            6 :     RIALTO_SERVER_LOG_MIL("Metrics: PlaybackState changed %s, %s -> %s", context.c_str(), oldState, newState);
     276              : 
     277              :     using std::chrono::duration_cast;
     278              :     using std::chrono::milliseconds;
     279              :     using std::chrono::steady_clock;
     280              :     const auto kNowMs{
     281            6 :         static_cast<std::uint64_t>(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count())};
     282              : 
     283            6 :     std::lock_guard<std::mutex> lock{m_mutex};
     284            6 :     auto sessionIter{m_sessionStates.find(context)};
     285            6 :     if (m_sessionStates.end() == sessionIter)
     286              :     {
     287              :         // First state notification for this session — create entry
     288            2 :         SessionMetricsState sessionState;
     289            2 :         sessionState.currentState = newState;
     290            2 :         sessionState.aggregator.begin(newState, kNowMs);
     291            2 :         m_sessionStates.emplace(context, std::move(sessionState));
     292            2 :         return;
     293              :     }
     294              : 
     295            4 :     auto &sessionState{sessionIter->second};
     296              : 
     297              :     // Finalize old state and emit report
     298            4 :     if (sessionState.aggregator.hasData() && m_reporter)
     299              :     {
     300            2 :         auto report{sessionState.aggregator.finalize(kNowMs)};
     301            2 :         StateTransitionReport transitionReport;
     302            2 :         transitionReport.context = context;
     303            2 :         transitionReport.metrics = report;
     304            2 :         m_reporter->reportStateTransition(transitionReport);
     305              :     }
     306              : 
     307            4 :     if (terminalState)
     308              :     {
     309              :         // Terminal state — remove session tracking
     310            2 :         m_sessionStates.erase(sessionIter);
     311              :     }
     312              :     else
     313              :     {
     314              :         // Begin accumulating for new state
     315            2 :         sessionState.currentState = newState;
     316            6 :         sessionState.aggregator.begin(newState, kNowMs);
     317              :     }
     318              : 
     319              :     // Request immediate sample for clean boundary
     320            4 :     m_client->requestMetricsSample(m_clientId, m_nextSampleId++, MetricsSampleReason::STATE_TRANSITION);
     321            6 : }
     322              : 
     323            2 : void MetricsCollector::notifyApplicationStateChanged(ApplicationState oldState, ApplicationState newState)
     324              : {
     325            2 :     RIALTO_SERVER_LOG_MIL("Metrics: ApplicationState changed %s -> %s", applicationStateToString(oldState),
     326              :                           applicationStateToString(newState));
     327              : 
     328              :     using std::chrono::duration_cast;
     329              :     using std::chrono::milliseconds;
     330              :     using std::chrono::steady_clock;
     331              :     const auto kNowMs{
     332            2 :         static_cast<std::uint64_t>(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count())};
     333              : 
     334            2 :     std::lock_guard<std::mutex> lock{m_mutex};
     335            2 :     m_currentApplicationState = newState;
     336              : 
     337            2 :     if (oldState == ApplicationState::RUNNING && newState != ApplicationState::RUNNING)
     338              :     {
     339              :         // Leaving RUNNING — finalize global aggregator
     340            1 :         if (m_globalAggregator.hasData() && m_reporter)
     341              :         {
     342            1 :             auto report{m_globalAggregator.finalize(kNowMs)};
     343            1 :             StateTransitionReport transitionReport;
     344            1 :             transitionReport.context = "global";
     345            1 :             transitionReport.metrics = report;
     346            1 :             m_reporter->reportStateTransition(transitionReport);
     347              :         }
     348            1 :         m_globalAggregator.reset();
     349              :     }
     350              : 
     351            2 :     if (newState == ApplicationState::RUNNING && oldState != ApplicationState::RUNNING)
     352              :     {
     353              :         // Entering RUNNING — start fresh global accumulation
     354            3 :         m_globalAggregator.begin(applicationStateToString(newState), kNowMs);
     355              :     }
     356              : 
     357              :     // Request immediate sample for clean boundary
     358            2 :     m_client->requestMetricsSample(m_clientId, m_nextSampleId++, MetricsSampleReason::STATE_TRANSITION);
     359              : }
     360              : 
     361            3 : MetricsCollector::ProcessMetricsSample MetricsCollector::getServerMetrics() const
     362              : {
     363              :     using std::chrono::duration_cast;
     364              :     using std::chrono::milliseconds;
     365              :     using std::chrono::steady_clock;
     366              :     using std::chrono::system_clock;
     367              : 
     368            3 :     struct tms processTimes
     369              :     {
     370              :     };
     371            3 :     const clock_t kCurrentTicks{times(&processTimes)};
     372            3 :     const int64_t kTicksPerSecond{sysconf(_SC_CLK_TCK)};
     373            3 :     std::uint64_t processCpuTimeMs{0};
     374            3 :     if ((static_cast<clock_t>(-1) != kCurrentTicks) && (kTicksPerSecond > 0))
     375              :     {
     376            3 :         const auto kProcessTicks{processTimes.tms_utime + processTimes.tms_stime};
     377            3 :         processCpuTimeMs = static_cast<std::uint64_t>((static_cast<double>(kProcessTicks) * 1000.0) /
     378            3 :                                                       static_cast<double>(kTicksPerSecond));
     379              :     }
     380              :     else
     381              :     {
     382            0 :         RIALTO_SERVER_LOG_WARN("Failed to sample server process CPU usage");
     383              :     }
     384              : 
     385            3 :     std::uint64_t processMemoryKb{0};
     386              :     {
     387            3 :         std::ifstream status{"/proc/self/status"};
     388            3 :         std::string line;
     389           69 :         while (std::getline(status, line))
     390              :         {
     391           69 :             if (line.rfind("VmRSS:", 0) == 0)
     392              :             {
     393            3 :                 if (std::sscanf(line.c_str(), "VmRSS: %" SCNu64, &processMemoryKb) != 1)
     394              :                 {
     395            0 :                     RIALTO_SERVER_LOG_WARN("Failed to parse server process memory usage");
     396              :                 }
     397            3 :                 break;
     398              :             }
     399              :         }
     400              :     }
     401              : 
     402            3 :     std::uint64_t cgroupMemoryUsageKb{0};
     403            3 :     std::uint64_t cgroupMemoryLimitKb{0};
     404              :     {
     405            6 :         auto readFileValue = [](const std::string &path) -> std::uint64_t
     406              :         {
     407            6 :             std::ifstream file{path};
     408            6 :             if (!file.is_open())
     409              :             {
     410            0 :                 return 0;
     411              :             }
     412            6 :             std::string content;
     413            6 :             if (!std::getline(file, content) || content.empty() || content == "max")
     414              :             {
     415            3 :                 return 0;
     416              :             }
     417            3 :             std::uint64_t value{0};
     418            3 :             if (std::sscanf(content.c_str(), "%" SCNu64, &value) == 1)
     419              :             {
     420            3 :                 return value;
     421              :             }
     422            0 :             return 0;
     423            6 :         };
     424              : 
     425              :         // Resolve the process's cgroup path from /proc/self/cgroup
     426              :         // cgroup v2 format: "0::<relative-path>"
     427            3 :         auto getCgroupBasePath = []() -> std::string
     428              :         {
     429            3 :             std::ifstream cgroupFile{"/proc/self/cgroup"};
     430            3 :             if (!cgroupFile.is_open())
     431              :             {
     432            0 :                 return {};
     433              :             }
     434            3 :             std::string line;
     435            3 :             while (std::getline(cgroupFile, line))
     436              :             {
     437              :                 // cgroup v2 line starts with "0::"
     438            3 :                 if (line.rfind("0::", 0) == 0)
     439              :                 {
     440            3 :                     std::string relativePath{line.substr(3)};
     441            3 :                     if (!relativePath.empty() && relativePath != "/")
     442              :                     {
     443            3 :                         return "/sys/fs/cgroup" + relativePath;
     444              :                     }
     445            0 :                     return "/sys/fs/cgroup";
     446            3 :                 }
     447              :             }
     448            0 :             return {};
     449            3 :         };
     450              : 
     451            3 :         std::uint64_t usageBytes{0};
     452            3 :         std::uint64_t limitBytes{0};
     453              : 
     454              :         // cgroup v2: read from process's own cgroup path
     455            3 :         std::string cgroupBase{getCgroupBasePath()};
     456            3 :         if (!cgroupBase.empty())
     457              :         {
     458            3 :             usageBytes = readFileValue(cgroupBase + "/memory.current");
     459            3 :             limitBytes = readFileValue(cgroupBase + "/memory.max");
     460              :         }
     461              : 
     462            3 :         if (usageBytes == 0)
     463              :         {
     464              :             // cgroup v1 fallback
     465            0 :             usageBytes = readFileValue("/sys/fs/cgroup/memory/memory.usage_in_bytes");
     466            0 :             limitBytes = readFileValue("/sys/fs/cgroup/memory/memory.limit_in_bytes");
     467              :         }
     468              : 
     469            3 :         cgroupMemoryUsageKb = usageBytes / 1024;
     470            3 :         cgroupMemoryLimitKb = limitBytes / 1024;
     471              :     }
     472              : 
     473            3 :     std::uint64_t shmMemoryKb{0};
     474              :     {
     475            3 :         std::ifstream smaps{"/proc/self/smaps_rollup"};
     476            3 :         std::string sline;
     477           21 :         while (std::getline(smaps, sline))
     478              :         {
     479           21 :             if (sline.rfind("Pss_Shmem:", 0) == 0)
     480              :             {
     481            3 :                 std::sscanf(sline.c_str(), "Pss_Shmem: %" SCNu64, &shmMemoryKb);
     482            3 :                 break;
     483              :             }
     484              :         }
     485              :     }
     486              : 
     487            3 :     return ProcessMetricsSample{static_cast<std::uint64_t>(
     488            3 :                                     duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count()),
     489            3 :                                 static_cast<std::uint64_t>(
     490            3 :                                     duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count()),
     491              :                                 processCpuTimeMs,
     492              :                                 processMemoryKb,
     493              :                                 cgroupMemoryUsageKb,
     494              :                                 cgroupMemoryLimitKb,
     495            6 :                                 shmMemoryKb};
     496              : }
     497              : 
     498            3 : double MetricsCollector::calculateCpuPercentage(std::uint64_t currentCpuTimeMs, std::uint64_t previousCpuTimeMs,
     499              :                                                 std::uint64_t currentMonotonicTimeMs,
     500              :                                                 std::uint64_t previousMonotonicTimeMs) const
     501              : {
     502            3 :     if ((currentCpuTimeMs < previousCpuTimeMs) || (currentMonotonicTimeMs <= previousMonotonicTimeMs))
     503              :     {
     504            0 :         return 0.0;
     505              :     }
     506              : 
     507            3 :     const auto kElapsedMs{currentMonotonicTimeMs - previousMonotonicTimeMs};
     508            3 :     if (kElapsedMs < kMinElapsedMs)
     509              :     {
     510              :         // Time delta too small for meaningful CPU percentage
     511            2 :         return 0.0;
     512              :     }
     513              : 
     514            1 :     return (static_cast<double>(currentCpuTimeMs - previousCpuTimeMs) / static_cast<double>(kElapsedMs)) * 100.0;
     515              : }
     516              : 
     517            3 : const char *MetricsCollector::sampleReasonToString(MetricsSampleReason reason)
     518              : {
     519            3 :     switch (reason)
     520              :     {
     521            1 :     case MetricsSampleReason::CONNECTED:
     522            1 :         return "CONNECTED";
     523            2 :     case MetricsSampleReason::PERIODIC:
     524            2 :         return "PERIODIC";
     525            0 :     case MetricsSampleReason::STATE_TRANSITION:
     526            0 :         return "STATE_TRANSITION";
     527            0 :     case MetricsSampleReason::UNKNOWN:
     528              :     default:
     529            0 :         return "UNKNOWN";
     530              :     }
     531              : }
     532              : 
     533            6 : const char *MetricsCollector::playbackStateToString(PlaybackState state)
     534              : {
     535            6 :     switch (state)
     536              :     {
     537            0 :     case PlaybackState::IDLE:
     538            0 :         return "IDLE";
     539            2 :     case PlaybackState::PLAYING:
     540            2 :         return "PLAYING";
     541            2 :     case PlaybackState::PAUSED:
     542            2 :         return "PAUSED";
     543            0 :     case PlaybackState::SEEKING:
     544            0 :         return "SEEKING";
     545            0 :     case PlaybackState::SEEK_DONE:
     546            0 :         return "SEEK_DONE";
     547            1 :     case PlaybackState::STOPPED:
     548            1 :         return "STOPPED";
     549            0 :     case PlaybackState::END_OF_STREAM:
     550            0 :         return "END_OF_STREAM";
     551            0 :     case PlaybackState::FAILURE:
     552            0 :         return "FAILURE";
     553            1 :     case PlaybackState::UNKNOWN:
     554              :     default:
     555            1 :         return "UNKNOWN";
     556              :     }
     557              : }
     558              : 
     559            6 : const char *MetricsCollector::webAudioPlayerStateToString(WebAudioPlayerState state)
     560              : {
     561            6 :     switch (state)
     562              :     {
     563            0 :     case WebAudioPlayerState::IDLE:
     564            0 :         return "IDLE";
     565            2 :     case WebAudioPlayerState::PLAYING:
     566            2 :         return "PLAYING";
     567            2 :     case WebAudioPlayerState::PAUSED:
     568            2 :         return "PAUSED";
     569            1 :     case WebAudioPlayerState::END_OF_STREAM:
     570            1 :         return "END_OF_STREAM";
     571            0 :     case WebAudioPlayerState::FAILURE:
     572            0 :         return "FAILURE";
     573            1 :     case WebAudioPlayerState::UNKNOWN:
     574              :     default:
     575            1 :         return "UNKNOWN";
     576              :     }
     577              : }
     578              : 
     579            6 : const char *MetricsCollector::applicationStateToString(ApplicationState state)
     580              : {
     581            6 :     switch (state)
     582              :     {
     583            4 :     case ApplicationState::RUNNING:
     584            4 :         return "RUNNING";
     585            1 :     case ApplicationState::INACTIVE:
     586            1 :         return "INACTIVE";
     587            1 :     case ApplicationState::UNKNOWN:
     588              :     default:
     589            1 :         return "UNKNOWN";
     590              :     }
     591              : }
     592              : } // namespace firebolt::rialto::server
        

Generated by: LCOV version 2.0-1