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 2023 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 "ClientController.h"
21 : #include "RialtoClientLogging.h"
22 : #include "SharedMemoryHandle.h"
23 : #include <algorithm>
24 : #include <chrono>
25 : #include <cinttypes>
26 : #include <cstdint>
27 : #include <cstdio>
28 : #include <cstring>
29 : #include <fstream>
30 : #include <sys/mman.h>
31 : #include <sys/times.h>
32 : #include <sys/un.h>
33 : #include <unistd.h>
34 : #include <utility>
35 :
36 : namespace
37 : {
38 : // The following error would be reported if a client is deleted
39 : // before unregisterClient() was called. Calling unregisterClient can
40 : // be automated via a proxy class (like the class MediaPipelineProxy)
41 : const std::string kClientPointerNotLocked{"A client could not be locked"};
42 : }; // namespace
43 :
44 : namespace firebolt::rialto::client
45 : {
46 1 : IClientControllerAccessor &IClientControllerAccessor::instance()
47 : {
48 1 : static ClientControllerAccessor factory;
49 1 : return factory;
50 : }
51 :
52 1 : IClientController &ClientControllerAccessor::getClientController() const
53 : {
54 2 : static ClientController ClientController{IControlIpcFactory::createFactory(),
55 3 : IPrivateMetricsIpcFactory::createFactory()};
56 0 : return ClientController;
57 : }
58 :
59 22 : ClientController::ClientController(const std::shared_ptr<IControlIpcFactory> &ControlIpcFactory,
60 22 : const std::shared_ptr<IPrivateMetricsIpcFactory> &privateMetricsIpcFactory)
61 22 : : m_currentState{ApplicationState::UNKNOWN}, m_registrationRequired{true}
62 : {
63 22 : RIALTO_CLIENT_LOG_DEBUG("entry:");
64 :
65 22 : const char kSrcRev[] = SRCREV;
66 22 : const char kTags[] = TAGS;
67 :
68 : if (std::strlen(kSrcRev) > 0)
69 : {
70 : if (std::strlen(kTags) > 0)
71 : {
72 : RIALTO_CLIENT_LOG_MIL("Release Tag(s): %s (Commit ID: %s)", kTags, kSrcRev);
73 : }
74 : else
75 : {
76 22 : RIALTO_CLIENT_LOG_MIL("Release Tag(s): No Release Tags! (Commit ID: %s)", kSrcRev);
77 : }
78 : }
79 : else
80 : {
81 : RIALTO_CLIENT_LOG_WARN("Failed to get git commit ID!");
82 : }
83 :
84 22 : m_controlIpc = ControlIpcFactory->createControlIpc(this);
85 21 : if (nullptr == m_controlIpc)
86 : {
87 1 : throw std::runtime_error("Failed to create the ControlIpc object");
88 : }
89 :
90 20 : m_privateMetricsIpc = privateMetricsIpcFactory->createPrivateMetricsIpc(this);
91 20 : if (nullptr == m_privateMetricsIpc)
92 : {
93 1 : throw std::runtime_error("Failed to create the PrivateMetricsIpc object");
94 : }
95 40 : }
96 :
97 38 : ClientController::~ClientController()
98 : {
99 19 : RIALTO_CLIENT_LOG_DEBUG("entry:");
100 :
101 19 : termSharedMemory();
102 38 : }
103 :
104 27 : std::shared_ptr<ISharedMemoryHandle> ClientController::getSharedMemoryHandle()
105 : {
106 27 : std::lock_guard<std::mutex> lock{m_mutex};
107 54 : return m_shmHandle;
108 27 : }
109 :
110 13 : bool ClientController::registerClient(std::weak_ptr<IControlClient> client, ApplicationState &appState)
111 : {
112 13 : std::shared_ptr<IControlClient> clientLocked = client.lock();
113 13 : if (!clientLocked)
114 : {
115 1 : RIALTO_CLIENT_LOG_ERROR("Client ptr is null");
116 1 : return false;
117 : }
118 :
119 12 : std::lock_guard<std::mutex> lock{m_mutex};
120 12 : if (m_registrationRequired)
121 : {
122 9 : if (!m_controlIpc->registerClient())
123 : {
124 1 : RIALTO_CLIENT_LOG_ERROR("Failed to register client");
125 1 : return false;
126 : }
127 : }
128 11 : m_registrationRequired = false;
129 :
130 11 : bool alreadyRegistered{std::find_if(m_clients.begin(), m_clients.end(),
131 4 : [&](auto &i)
132 : {
133 4 : std::shared_ptr<IControlClient> iLocked = i.lock();
134 4 : return (iLocked == clientLocked);
135 26 : }) != m_clients.end()};
136 11 : if (!alreadyRegistered)
137 7 : m_clients.push_back(client);
138 11 : appState = m_currentState;
139 :
140 11 : return true;
141 13 : }
142 :
143 1 : bool ClientController::unregisterClient(std::weak_ptr<IControlClient> client)
144 : {
145 1 : std::shared_ptr<IControlClient> clientLocked = client.lock();
146 1 : if (!clientLocked)
147 : {
148 0 : RIALTO_CLIENT_LOG_ERROR("Client ptr is null");
149 0 : return false;
150 : }
151 :
152 1 : std::lock_guard<std::mutex> lock{m_mutex};
153 :
154 1 : bool found{false};
155 1 : for (auto i = m_clients.begin(); i != m_clients.end();)
156 : {
157 1 : std::shared_ptr<IControlClient> iLocked = i->lock();
158 1 : if (!iLocked)
159 : {
160 0 : RIALTO_CLIENT_LOG_ERROR("%s", kClientPointerNotLocked.c_str());
161 0 : i = m_clients.erase(i);
162 : }
163 1 : else if (iLocked == clientLocked)
164 : {
165 1 : i = m_clients.erase(i);
166 1 : found = true;
167 1 : break;
168 : }
169 : else
170 0 : ++i;
171 1 : }
172 :
173 1 : if (!found)
174 : {
175 0 : RIALTO_CLIENT_LOG_ERROR("Client not found");
176 0 : return false;
177 : }
178 :
179 1 : return true;
180 : }
181 :
182 11 : bool ClientController::initSharedMemory()
183 : try
184 : {
185 11 : std::lock_guard<std::mutex> lock{m_mutex};
186 11 : int32_t shmFd{-1};
187 11 : uint32_t shmBufferLen{0U};
188 11 : if (!m_controlIpc->getSharedMemory(shmFd, shmBufferLen))
189 : {
190 1 : RIALTO_CLIENT_LOG_ERROR("Failed to get the shared memory");
191 1 : return false;
192 : }
193 10 : m_shmHandle = std::make_shared<SharedMemoryHandle>(shmFd, shmBufferLen);
194 :
195 8 : RIALTO_CLIENT_LOG_INFO("Shared buffer was successfully initialised");
196 8 : return true;
197 11 : }
198 2 : catch (const std::exception &e)
199 : {
200 2 : RIALTO_CLIENT_LOG_ERROR("Failed to initialise shared memory: %s", e.what());
201 2 : return false;
202 : }
203 :
204 26 : void ClientController::termSharedMemory()
205 : {
206 26 : std::lock_guard<std::mutex> lock{m_mutex};
207 26 : m_shmHandle.reset();
208 : }
209 :
210 19 : void ClientController::notifyApplicationState(ApplicationState state)
211 : {
212 : {
213 19 : std::lock_guard<std::mutex> lock{m_mutex};
214 19 : if (ApplicationState::UNKNOWN == state)
215 : {
216 3 : RIALTO_CLIENT_LOG_DEBUG("Application state changed to unknown. Client will have to register next time");
217 3 : m_registrationRequired = true;
218 : }
219 19 : if (m_currentState == state)
220 : {
221 1 : RIALTO_CLIENT_LOG_WARN("Rialto application state already set, %s", stateToString(m_currentState).c_str());
222 1 : return;
223 : }
224 19 : }
225 :
226 18 : switch (state)
227 : {
228 11 : case ApplicationState::RUNNING:
229 : {
230 11 : if (!initSharedMemory())
231 : {
232 3 : RIALTO_CLIENT_LOG_ERROR("Could not initalise the shared memory");
233 3 : return;
234 : }
235 : // Inform clients after memory initialisation
236 8 : changeStateAndNotifyClients(state);
237 8 : break;
238 : }
239 7 : case ApplicationState::INACTIVE:
240 : case ApplicationState::UNKNOWN:
241 : {
242 : // Inform clients before memory termination
243 7 : changeStateAndNotifyClients(state);
244 7 : termSharedMemory();
245 7 : break;
246 : }
247 : }
248 : }
249 :
250 31 : std::string ClientController::stateToString(ApplicationState state)
251 : {
252 31 : switch (state)
253 : {
254 14 : case ApplicationState::RUNNING:
255 : {
256 28 : return "RUNNING";
257 : }
258 5 : case ApplicationState::INACTIVE:
259 : {
260 10 : return "INACTIVE";
261 : }
262 12 : case ApplicationState::UNKNOWN:
263 : default:
264 : {
265 24 : return "UNKNOWN";
266 : }
267 : }
268 : }
269 :
270 15 : void ClientController::changeStateAndNotifyClients(ApplicationState state)
271 : {
272 15 : std::vector<std::shared_ptr<IControlClient>> currentClients;
273 : {
274 15 : std::lock_guard<std::mutex> lock{m_mutex};
275 15 : RIALTO_CLIENT_LOG_MIL("Rialto application state changed from %s to %s", stateToString(m_currentState).c_str(),
276 : stateToString(state).c_str());
277 15 : m_currentState = state;
278 22 : for (const std::weak_ptr<IControlClient> &client : m_clients)
279 : {
280 7 : std::shared_ptr<IControlClient> clientLocked{client.lock()};
281 7 : if (clientLocked)
282 : {
283 7 : currentClients.push_back(std::move(clientLocked));
284 : }
285 : else
286 : {
287 0 : RIALTO_CLIENT_LOG_ERROR("%s", kClientPointerNotLocked.c_str());
288 : }
289 7 : }
290 15 : }
291 22 : for (const auto &client : currentClients)
292 : {
293 7 : client->notifyApplicationState(state);
294 : }
295 15 : }
296 :
297 1 : void ClientController::reportClientMetrics(std::uint64_t sampleId, std::uint32_t reason)
298 : {
299 2 : if (!m_privateMetricsIpc->reportClientMetrics(sampleId, reason, getProcessName(),
300 1 : static_cast<std::uint32_t>(getpid()), getMonotonicTimeMs(),
301 : getEpochTimeMs(), getProcessCpuTimeMs(), getProcessMemoryKb()))
302 : {
303 0 : RIALTO_CLIENT_LOG_DEBUG("Failed to report client process metrics");
304 : }
305 1 : }
306 :
307 1 : std::uint64_t ClientController::getMonotonicTimeMs() const
308 : {
309 : using std::chrono::duration_cast;
310 : using std::chrono::milliseconds;
311 : using std::chrono::steady_clock;
312 :
313 1 : return static_cast<std::uint64_t>(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count());
314 : }
315 :
316 1 : std::uint64_t ClientController::getEpochTimeMs() const
317 : {
318 : using std::chrono::duration_cast;
319 : using std::chrono::milliseconds;
320 : using std::chrono::system_clock;
321 :
322 1 : return static_cast<std::uint64_t>(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
323 : }
324 :
325 1 : std::uint64_t ClientController::getProcessCpuTimeMs() const
326 : {
327 1 : struct tms processTimes = {0};
328 1 : const clock_t kCurrentTicks{times(&processTimes)};
329 1 : const int64_t kTicksPerSecond{sysconf(_SC_CLK_TCK)};
330 1 : if ((static_cast<clock_t>(-1) == kCurrentTicks) || (kTicksPerSecond <= 0))
331 : {
332 0 : RIALTO_CLIENT_LOG_WARN("Failed to sample client process CPU usage");
333 0 : return 0;
334 : }
335 :
336 1 : const auto kProcessTicks{processTimes.tms_utime + processTimes.tms_stime};
337 1 : return static_cast<std::uint64_t>((static_cast<double>(kProcessTicks) * 1000.0) /
338 1 : static_cast<double>(kTicksPerSecond));
339 : }
340 :
341 1 : std::string ClientController::getProcessName() const
342 : {
343 1 : std::ifstream comm{"/proc/self/comm"};
344 1 : std::string processName;
345 1 : if (std::getline(comm, processName) && !processName.empty())
346 : {
347 1 : return processName;
348 : }
349 0 : return "unknown";
350 1 : }
351 :
352 1 : std::uint64_t ClientController::getProcessMemoryKb() const
353 : {
354 1 : std::ifstream status{"/proc/self/status"};
355 1 : std::string line;
356 24 : while (std::getline(status, line))
357 : {
358 23 : if (line.rfind("VmRSS:", 0) == 0)
359 : {
360 1 : std::uint64_t memKb{0};
361 1 : if (std::sscanf(line.c_str(), "VmRSS: %" SCNu64, &memKb) == 1)
362 : {
363 1 : return memKb;
364 : }
365 : }
366 : }
367 0 : RIALTO_CLIENT_LOG_WARN("Failed to sample client process memory usage");
368 0 : return 0;
369 1 : }
370 : } // namespace firebolt::rialto::client
|