LCOV - code coverage report
Current view: top level - media/server/service/source - PrivateMetricsService.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 93.0 % 100 93
Test Date: 2026-09-11 17:36:12 Functions: 100.0 % 9 9

            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 "PrivateMetricsService.h"
      21              : #include "RialtoServerLogging.h"
      22              : #include <cinttypes>
      23              : #include <cstdio>
      24              : #include <fstream>
      25              : #include <string>
      26              : #include <utility>
      27              : 
      28              : namespace firebolt::rialto::server::service
      29              : {
      30           12 : PrivateMetricsService::PrivateMetricsService(
      31           12 :     std::shared_ptr<firebolt::rialto::server::IMetricsCollectorFactory> collectorFactory)
      32           12 :     : m_collectorFactory{std::move(collectorFactory)}
      33              : {
      34              : }
      35              : 
      36           19 : PrivateMetricsService::~PrivateMetricsService()
      37              : {
      38           12 :     std::lock_guard<std::mutex> lock{m_mutex};
      39           12 :     m_collectors.clear();
      40           19 : }
      41              : 
      42            5 : void PrivateMetricsService::clientReady(int clientId,
      43              :                                         const std::shared_ptr<firebolt::rialto::server::IMetricsCollectorClient> &client)
      44              : {
      45            5 :     std::lock_guard<std::mutex> lock{m_mutex};
      46            5 :     if (!m_collectorFactory)
      47              :     {
      48            0 :         RIALTO_SERVER_LOG_ERROR("MetricsCollectorFactory is null; cannot create MetricsCollector for client %d",
      49              :                                 clientId);
      50            0 :         return;
      51              :     }
      52            5 :     auto collector = m_collectorFactory->create(clientId, client, m_currentApplicationState);
      53            5 :     if (collector)
      54              :     {
      55            4 :         m_collectors.emplace(clientId, std::move(collector));
      56            4 :         RIALTO_SERVER_LOG_INFO("MetricsCollector created for client %d", clientId);
      57              :     }
      58              :     else
      59              :     {
      60            1 :         RIALTO_SERVER_LOG_ERROR("Failed to create MetricsCollector for client %d", clientId);
      61              :     }
      62            5 : }
      63              : 
      64            2 : void PrivateMetricsService::clientDisconnected(int clientId)
      65              : {
      66            2 :     std::lock_guard<std::mutex> lock{m_mutex};
      67            2 :     auto iter = m_collectors.find(clientId);
      68            2 :     if (iter != m_collectors.end())
      69              :     {
      70            1 :         m_collectors.erase(iter);
      71            1 :         RIALTO_SERVER_LOG_INFO("MetricsCollector destroyed for client %d", clientId);
      72              :     }
      73            2 : }
      74              : 
      75            2 : void PrivateMetricsService::reportMetrics(int clientId, const firebolt::rialto::server::ClientMetricsData &metrics)
      76              : {
      77            2 :     std::lock_guard<std::mutex> lock{m_mutex};
      78            2 :     auto iter = m_collectors.find(clientId);
      79            2 :     if (iter != m_collectors.end())
      80              :     {
      81            1 :         iter->second->processMetrics(metrics);
      82              :     }
      83              :     else
      84              :     {
      85            1 :         RIALTO_SERVER_LOG_WARN("reportMetrics for unknown client %d", clientId);
      86              :     }
      87            2 : }
      88              : 
      89            2 : void PrivateMetricsService::notifyPlaybackStateChanged(int sessionId, PlaybackState oldState, PlaybackState newState)
      90              : {
      91            2 :     std::lock_guard<std::mutex> lock{m_mutex};
      92            3 :     for (auto &[clientId, collector] : m_collectors)
      93              :     {
      94            1 :         collector->notifyPlaybackStateChanged(sessionId, oldState, newState);
      95              :     }
      96            2 : }
      97              : 
      98            1 : void PrivateMetricsService::notifyWebAudioPlayerStateChanged(int handle, WebAudioPlayerState oldState,
      99              :                                                              WebAudioPlayerState newState)
     100              : {
     101            1 :     std::lock_guard<std::mutex> lock{m_mutex};
     102            2 :     for (auto &[clientId, collector] : m_collectors)
     103              :     {
     104            1 :         collector->notifyWebAudioPlayerStateChanged(handle, oldState, newState);
     105              :     }
     106              : }
     107              : 
     108            5 : void PrivateMetricsService::notifyApplicationStateChanged(ApplicationState newState)
     109              : {
     110              :     {
     111            5 :         std::lock_guard<std::mutex> lock{m_mutex};
     112              :         // Keep transition history here so upstream services only report the new application state.
     113            5 :         const auto oldState{m_currentApplicationState};
     114            5 :         m_currentApplicationState = newState;
     115            8 :         for (auto &[clientId, collector] : m_collectors)
     116              :         {
     117            3 :             collector->notifyApplicationStateChanged(oldState, newState);
     118              :         }
     119            5 :     }
     120              : 
     121              :     // When transitioning to INACTIVE, record a server-side memory snapshot.
     122              :     // At this point, pipelines and shared memory have already been freed but
     123              :     // no client may be connected to supply a full sample — so we read the
     124              :     // server's own memory directly. The collector mutex is released first so
     125              :     // filesystem access cannot block metrics processing.
     126            5 :     if (newState == ApplicationState::INACTIVE)
     127              :     {
     128            2 :         std::uint64_t serverMemoryKb{0};
     129              :         {
     130            2 :             std::ifstream status{"/proc/self/status"};
     131            2 :             std::string line;
     132           46 :             while (std::getline(status, line))
     133              :             {
     134           46 :                 if (line.rfind("VmRSS:", 0) == 0)
     135              :                 {
     136            2 :                     std::sscanf(line.c_str(), "VmRSS: %" SCNu64, &serverMemoryKb);
     137            2 :                     break;
     138              :                 }
     139              :             }
     140              :         }
     141              : 
     142            2 :         std::uint64_t cgroupMemoryUsageKb{0};
     143              :         {
     144            2 :             auto readFileValue = [](const std::string &path) -> std::uint64_t
     145              :             {
     146            2 :                 std::ifstream file{path};
     147            2 :                 if (!file.is_open())
     148              :                 {
     149            0 :                     return 0;
     150              :                 }
     151            2 :                 std::string content;
     152            2 :                 if (!std::getline(file, content) || content.empty() || content == "max")
     153              :                 {
     154            0 :                     return 0;
     155              :                 }
     156            2 :                 std::uint64_t value{0};
     157            2 :                 if (std::sscanf(content.c_str(), "%" SCNu64, &value) == 1)
     158              :                 {
     159            2 :                     return value;
     160              :                 }
     161            0 :                 return 0;
     162            2 :             };
     163              : 
     164              :             // Resolve the process's cgroup path from /proc/self/cgroup
     165            2 :             std::ifstream cgroupFile{"/proc/self/cgroup"};
     166            2 :             std::string cgroupBase;
     167            2 :             if (cgroupFile.is_open())
     168              :             {
     169            2 :                 std::string line;
     170            2 :                 while (std::getline(cgroupFile, line))
     171              :                 {
     172            2 :                     if (line.rfind("0::", 0) == 0)
     173              :                     {
     174            2 :                         std::string relativePath{line.substr(3)};
     175            2 :                         if (!relativePath.empty() && relativePath != "/")
     176              :                         {
     177            2 :                             cgroupBase = "/sys/fs/cgroup" + relativePath;
     178              :                         }
     179              :                         else
     180              :                         {
     181            0 :                             cgroupBase = "/sys/fs/cgroup";
     182              :                         }
     183            2 :                         break;
     184              :                     }
     185              :                 }
     186              :             }
     187              : 
     188            2 :             std::uint64_t usageBytes{0};
     189            2 :             if (!cgroupBase.empty())
     190              :             {
     191            2 :                 usageBytes = readFileValue(cgroupBase + "/memory.current");
     192              :             }
     193            2 :             if (usageBytes == 0)
     194              :             {
     195            0 :                 usageBytes = readFileValue("/sys/fs/cgroup/memory/memory.usage_in_bytes");
     196              :             }
     197            2 :             cgroupMemoryUsageKb = usageBytes / 1024;
     198              :         }
     199              : 
     200              :         // Read smaps_rollup to split private-dirty heap from file-backed libs.
     201            2 :         std::uint64_t anonKb{0}, sharedCleanKb{0}, privateCleanKb{0}, privateDirtyKb{0};
     202              :         {
     203            2 :             std::ifstream smaps{"/proc/self/smaps_rollup"};
     204            2 :             std::string sline;
     205           48 :             while (std::getline(smaps, sline))
     206              :             {
     207           46 :                 if (sline.rfind("Anonymous:", 0) == 0)
     208            2 :                     std::sscanf(sline.c_str(), "Anonymous: %" SCNu64, &anonKb);
     209           44 :                 else if (sline.rfind("Shared_Clean:", 0) == 0)
     210            2 :                     std::sscanf(sline.c_str(), "Shared_Clean: %" SCNu64, &sharedCleanKb);
     211           42 :                 else if (sline.rfind("Private_Clean:", 0) == 0)
     212            2 :                     std::sscanf(sline.c_str(), "Private_Clean: %" SCNu64, &privateCleanKb);
     213           40 :                 else if (sline.rfind("Private_Dirty:", 0) == 0)
     214            2 :                     std::sscanf(sline.c_str(), "Private_Dirty: %" SCNu64, &privateDirtyKb);
     215              :             }
     216              :         }
     217            2 :         RIALTO_SERVER_LOG_MIL("Metrics: INACTIVE memory snapshot — server_mem_kb=%" PRIu64 ", cgroup_mem_kb=%" PRIu64
     218              :                               ", anon_kb=%" PRIu64 ", private_dirty_kb=%" PRIu64 ", private_clean_kb=%" PRIu64
     219              :                               ", shared_clean_kb=%" PRIu64,
     220              :                               serverMemoryKb, cgroupMemoryUsageKb, anonKb, privateDirtyKb, privateCleanKb, sharedCleanKb);
     221              :     }
     222            5 : }
     223              : } // namespace firebolt::rialto::server::service
        

Generated by: LCOV version 2.0-1