KCGroups

kapplicationscope.cpp
1// SPDX-FileCopyrightText: 2020 Henri Chain <henri.chain@enioka.com>
2// SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
3//
4// SPDX-License-Identifier: LGPL-2.1-or-later
5
6#include "kapplicationscope.h"
7#include "kapplicationscope_p.h"
8#include "kcgroups_debug.h"
9#include "managerinterface.h"
10#include <limits>
11
12static const Property<OptionalQULongLong> cpuQuotaProp = {&KApplicationScopePrivate::m_cpuQuota,
14 QStringLiteral("CPUQuotaPerSecUSec"),
15 true,
16 std::numeric_limits<qulonglong>::max()};
17
18static const Property<OptionalQULongLong> cpuQuotaPeriodProp = {&KApplicationScopePrivate::m_cpuQuotaPeriod,
20 QStringLiteral("CPUQuotaPeriodUSec"),
21 true,
22 std::numeric_limits<qulonglong>::max()};
23
24static const Property<OptionalQULongLong> cpuWeightProp = {&KApplicationScopePrivate::m_cpuWeight,
26 QStringLiteral("CPUWeight"),
27 true,
28 std::numeric_limits<qulonglong>::max()};
29
30static const Property<OptionalQULongLong> ioWeightProp = {&KApplicationScopePrivate::m_ioWeight,
32 QStringLiteral("IOWeight"),
33 true,
34 std::numeric_limits<qulonglong>::max()};
35
36static const Property<OptionalQULongLong> memoryLowProp = {&KApplicationScopePrivate::m_memoryLow,
38 QStringLiteral("MemoryLow"),
39 true,
40 std::numeric_limits<qulonglong>::max()};
41
42static const Property<OptionalQULongLong> memoryHighProp = {&KApplicationScopePrivate::m_memoryHigh,
44 QStringLiteral("MemoryHigh"),
45 true,
46 std::numeric_limits<qulonglong>::max()};
47
48static const Property<OptionalQULongLong> memoryMinProp = {&KApplicationScopePrivate::m_memoryMin,
50 QStringLiteral("MemoryMin"),
51 true,
52 std::numeric_limits<qulonglong>::max()};
53
54static const Property<OptionalQULongLong> memoryMaxProp = {&KApplicationScopePrivate::m_memoryMax,
56 QStringLiteral("MemoryMax"),
57 true,
58 std::numeric_limits<qulonglong>::max()};
59
60static const Property<OptionalQULongLong> memorySwapMaxProp = {&KApplicationScopePrivate::m_memorySwapMax,
62 QStringLiteral("MemorySwapMax"),
63 true,
64 std::numeric_limits<qulonglong>::max()};
65
66static const QHash<QString, const Property<OptionalQULongLong> *> qullProps = {
67 {cpuQuotaProp.systemdName, &cpuQuotaProp},
68 {cpuQuotaPeriodProp.systemdName, &cpuQuotaPeriodProp},
69 {cpuWeightProp.systemdName, &cpuWeightProp},
70 {ioWeightProp.systemdName, &ioWeightProp},
71 {memoryLowProp.systemdName, &memoryLowProp},
72 {memoryHighProp.systemdName, &memoryHighProp},
73 {memoryMinProp.systemdName, &memoryMinProp},
74 {memoryMaxProp.systemdName, &memoryMaxProp},
75 {memorySwapMaxProp.systemdName, &memorySwapMaxProp},
76};
77
79 : QObject(parent)
80 , d_ptr(new KApplicationScopePrivate(path, id, this))
81{
82}
83
88
90{
91 return d_ptr->m_path;
92}
93
95{
96 return d_ptr->m_id;
97}
98
100{
101 return d_ptr->m_cgroup;
102}
103
105{
106 return d_ptr->m_description;
107}
108
110{
111 return d_ptr->m_desktopName;
112}
113
115{
116 return d_ptr->m_desktopFilePath;
117}
118
120{
121 return d_ptr->m_instance;
122}
123
125{
126 return d_ptr->m_lastError;
127}
128
129OptionalQULongLong KApplicationScope::cpuQuota() const
130{
131 return d_ptr->getProperty<OptionalQULongLong>(cpuQuotaProp);
132}
133
134OptionalQULongLong KApplicationScope::cpuQuotaPeriod() const
135{
136 return d_ptr->getProperty<OptionalQULongLong>(cpuQuotaPeriodProp);
137}
138
139OptionalQULongLong KApplicationScope::cpuWeight() const
140{
141 return d_ptr->getProperty<OptionalQULongLong>(cpuWeightProp);
142}
143
144OptionalQULongLong KApplicationScope::ioWeight() const
145{
146 return d_ptr->getProperty<OptionalQULongLong>(ioWeightProp);
147}
148
149OptionalQULongLong KApplicationScope::memoryLow() const
150{
151 return d_ptr->getProperty<OptionalQULongLong>(memoryLowProp);
152}
153
154OptionalQULongLong KApplicationScope::memoryHigh() const
155{
156 return d_ptr->getProperty<OptionalQULongLong>(memoryHighProp);
157}
158
159OptionalQULongLong KApplicationScope::memoryMin() const
160{
161 return d_ptr->getProperty<OptionalQULongLong>(memoryMinProp);
162}
163
164OptionalQULongLong KApplicationScope::memoryMax() const
165{
166 return d_ptr->getProperty<OptionalQULongLong>(memoryMaxProp);
167}
168
169OptionalQULongLong KApplicationScope::memorySwapMax() const
170{
171 return d_ptr->getProperty<OptionalQULongLong>(memorySwapMaxProp);
172}
173
174void KApplicationScope::setCpuQuota(const OptionalQULongLong &quota)
175{
176 d_ptr->trySetProperty<OptionalQULongLong>(cpuQuotaProp, quota);
177}
178
179void KApplicationScope::setCpuQuotaPeriod(const OptionalQULongLong &period)
180{
181 d_ptr->trySetProperty<OptionalQULongLong>(cpuQuotaPeriodProp, period);
182}
183
184void KApplicationScope::setCpuWeight(const OptionalQULongLong &weight)
185{
186 d_ptr->trySetProperty<OptionalQULongLong>(cpuWeightProp, weight);
187}
188
189void KApplicationScope::setIoWeight(const OptionalQULongLong &weight)
190{
191 d_ptr->trySetProperty<OptionalQULongLong>(ioWeightProp, weight);
192}
193
194void KApplicationScope::setMemoryLow(const OptionalQULongLong &memoryLow)
195{
196 d_ptr->trySetProperty<OptionalQULongLong>(memoryLowProp, memoryLow);
197}
198
199void KApplicationScope::setMemoryHigh(const OptionalQULongLong &memoryHigh)
200{
201 d_ptr->trySetProperty<OptionalQULongLong>(memoryHighProp, memoryHigh);
202}
203
204void KApplicationScope::setMemoryMin(const OptionalQULongLong &memoryMin)
205{
206 d_ptr->trySetProperty<OptionalQULongLong>(memoryMinProp, memoryMin);
207}
208
209void KApplicationScope::setMemoryMax(const OptionalQULongLong &memoryMax)
210{
211 d_ptr->trySetProperty<OptionalQULongLong>(memoryMaxProp, memoryMax);
212}
213
214void KApplicationScope::setMemorySwapMax(const OptionalQULongLong &memorySwapMax)
215{
216 d_ptr->trySetProperty<OptionalQULongLong>(memorySwapMaxProp, memorySwapMax);
217}
218
220{
221 d_ptr->stop();
222}
223
224KApplicationScope::~KApplicationScope()
225{
226 delete d_ptr;
227}
228
229static const auto systemd1 = QStringLiteral("org.freedesktop.systemd1");
230static const auto systemd1Scope = QStringLiteral("org.freedesktop.systemd1.Scope");
231static const auto systemd1Service = QStringLiteral("org.freedesktop.systemd1.Service");
232static const auto systemd1Slice = QStringLiteral("org.freedesktop.systemd1.Slice");
233static const auto systemd1Unit = QStringLiteral("org.freedesktop.systemd1.Unit");
234static const auto systemd1Path = QStringLiteral("/org/freedesktop/systemd1");
235
237{
238 org::freedesktop::systemd1::Manager manager(systemd1, systemd1Path, QDBusConnection::sessionBus());
239 auto reply = manager.GetUnitByPID(pid);
240 reply.waitForFinished();
241
242 if (reply.isError()) {
243 qCWarning(KCGROUPS_LOG) << "Cannot get app from pid:" << reply.error().message();
244 return nullptr;
245 } else {
246 return new KApplicationScope(reply.argumentAt<0>().path(), parent);
247 }
248}
249
250KApplicationScopePrivate::KApplicationScopePrivate(const QString &path, const QString &id, KApplicationScope *parent)
251 : m_lastError(KApplicationScope::NoError)
252 , m_path(path)
253 , m_id(id)
254 , q_ptr(parent)
255 , m_unit(new org::freedesktop::systemd1::Unit(systemd1, path, QDBusConnection::sessionBus(), q_ptr))
256 , m_properties(new org::freedesktop::DBus::Properties(systemd1, path, QDBusConnection::sessionBus(), q_ptr))
257{
258 parseId();
259 qDBusRegisterMetaType<QVariantMultiMap>();
260 qDBusRegisterMetaType<QVariantMultiItem>();
261
262 // Try to fill cache for all properties.
263 const auto interface = path.endsWith(QStringLiteral("_2escope")) ? systemd1Scope
264 : path.endsWith(QStringLiteral("_2eslice")) ? systemd1Slice
265 : systemd1Service;
266 const auto *getAllWatcher = new QDBusPendingCallWatcher(m_properties->GetAll(interface), q_ptr);
267 QObject::connect(getAllWatcher, &QDBusPendingCallWatcher::finished, q_ptr, [this](QDBusPendingCallWatcher *w) {
268 handleGetAllCallFinished(w);
269 });
270
271 const auto *unitGetAllWatcher = new QDBusPendingCallWatcher(m_properties->GetAll(systemd1Unit));
272 QObject::connect(unitGetAllWatcher, &QDBusPendingCallWatcher::finished, q_ptr, [this](QDBusPendingCallWatcher *w) {
273 handleGetUnitCallFinished(w);
274 });
275}
276
277static const QRegularExpression appPattern(QStringLiteral("^apps?-(.+?)(?:-([^-]+))?\\.(scope|service|slice)$"));
278
279void KApplicationScopePrivate::parseId()
280{
281 const auto match = appPattern.match(m_id);
282 if (match.hasMatch()) {
283 auto name = match.captured(1);
284 static const QRegularExpression escaped(QStringLiteral("\\\\x([0-9a-f]{2})"));
285 auto escapedBytes = escaped.globalMatch(name);
286 int offset = 0;
287 while (escapedBytes.hasNext()) {
288 bool ok;
289 const auto escapedMatch = escapedBytes.next();
290 const char byte = escapedMatch.captured(1).toUInt(&ok, 16);
291 if (ok) {
292 name.replace(offset + escapedMatch.capturedStart(), escapedMatch.capturedLength(), QLatin1Char(byte));
293 offset -= escapedMatch.capturedLength() - 1;
294 }
295 }
296 m_desktopName = name;
297 m_instance = match.captured(2);
298 }
299}
300
301template<typename T>
302T KApplicationScopePrivate::nullIfDefault(const Property<T> &prop, const QVariant &variant)
303{
304 using value_type = typename T::value_type;
305 const auto value = variant.value<value_type>();
306 if (variant.isNull() || value == prop.defaultValue) {
307 return T();
308 }
309 return value;
310}
311
312template<typename T>
313QVariant KApplicationScopePrivate::defaultIfNull(const Property<T> &prop, const T &opt)
314{
315 // Convert null value to default if there is one
316 return opt ? *opt : prop.hasDefault ? prop.defaultValue : QVariant();
317}
318
319template<typename T>
320T KApplicationScopePrivate::getProperty(const Property<T> &prop)
321{
322 return this->*prop.privateMember;
323}
324
325template<typename T>
326void KApplicationScopePrivate::saveProperty(const Property<T> &prop, const T &opt)
327{
328 this->*prop.privateMember = opt;
329 emit(q_ptr->*prop.changedSignal)(opt);
330 emit q_ptr->propertyChanged(prop.systemdName);
331}
332
333template<typename T>
334void KApplicationScopePrivate::saveIfNull(const Property<T> &prop, const QVariant &variant)
335{
336 const auto opt = nullIfDefault(prop, variant);
337 const auto cur = this->*prop.privateMember;
338 // Avoid race condition when property was set in the meantime
339 if (!cur && opt != cur) {
340 saveProperty(prop, opt);
341 }
342}
343
344template<typename T>
345void KApplicationScopePrivate::trySetProperty(const Property<T> &prop, T opt)
346{
347 if (this->*prop.privateMember != opt) {
348 saveProperty(prop, opt);
349 const auto reply = m_unit->SetProperties(true, {{prop.systemdName, defaultIfNull(prop, opt)}});
350 const auto *watcher = new QDBusPendingCallWatcher(reply, q_ptr);
352 handleVoidCallFinished(watcher, KApplicationScope::SetFailedError);
353 });
354 }
355}
356
357void KApplicationScopePrivate::stop()
358{
359 const auto *watcher = new QDBusPendingCallWatcher(m_unit->Stop(QStringLiteral("replace")), q_ptr);
361 handleVoidCallFinished(watcher, KApplicationScope::StopFailedError);
362 });
363}
364
365void KApplicationScopePrivate::handleVoidCallFinished(QDBusPendingCallWatcher *call, KApplicationScope::ErrorCode code)
366{
367 QDBusPendingReply<> reply = *call;
368 if (reply.isError()) {
369 setError(code, reply.error().message());
370 } else {
371 qCDebug(KCGROUPS_LOG) << "void call finished async";
372 }
373 call->deleteLater();
374}
375
376void KApplicationScopePrivate::handleGetAllCallFinished(QDBusPendingCallWatcher *call)
377{
378 QDBusPendingReply<QVariantMap> reply = *call;
379 if (reply.isError()) {
381 } else {
382 qCDebug(KCGROUPS_LOG) << "getall finished async";
383 const QVariantMap properties = reply.argumentAt<0>();
384 for (auto kv = properties.constKeyValueBegin(); kv != properties.constKeyValueEnd(); kv++) {
385 auto k = (*kv).first;
386 auto v = (*kv).second;
387 if (qullProps.contains(k)) {
388 saveIfNull<OptionalQULongLong>(*qullProps[k], v);
389 } else if (k == QStringLiteral("ControlGroup")) {
390 m_cgroup = QStringLiteral("/sys/fs/cgroup/systemd") + v.toString();
391 emit q_ptr->cgroupChanged(m_cgroup);
392 emit q_ptr->propertyChanged(k);
393 }
394 }
395 }
396 call->deleteLater();
397}
398
399void KApplicationScopePrivate::handleGetUnitCallFinished(QDBusPendingCallWatcher *call)
400{
401 QDBusPendingReply<QVariantMap> reply = *call;
402 if (reply.isError()) {
404 } else {
405 qCDebug(KCGROUPS_LOG) << "getall unit finished async";
406 const QVariantMap properties = reply.argumentAt<0>();
407 for (auto kv = properties.constKeyValueBegin(); kv != properties.constKeyValueEnd(); kv++) {
408 auto k = (*kv).first;
409 auto v = (*kv).second.toString();
410 if (k == QStringLiteral("Id")) {
411 // If id not passed as constructor argument
412 if (m_id.isNull()) {
413 m_id = v;
414 parseId();
415 emit q_ptr->idChanged(m_id);
416 emit q_ptr->propertyChanged(k);
417 if (!m_desktopName.isNull()) {
418 emit q_ptr->desktopNameChanged(m_desktopName);
419 }
420 }
421 } else if (k == QStringLiteral("Description")) {
422 if (v != m_id) {
423 m_description = v;
424 emit q_ptr->descriptionChanged(v);
425 emit q_ptr->propertyChanged(k);
426 }
427 } else if (k == QStringLiteral("SourcePath")) {
428 if (v.endsWith(QStringLiteral(".desktop"))) {
429 m_desktopFilePath = v;
430 m_desktopName = QFileInfo(v).fileName().chopped(strlen(".desktop"));
431 emit q_ptr->desktopFilePathChanged(v);
432 emit q_ptr->propertyChanged(k);
433 emit q_ptr->desktopNameChanged(m_desktopName);
434 }
435 }
436 }
437 }
438 call->deleteLater();
439}
440
441void KApplicationScopePrivate::setError(KApplicationScope::ErrorCode code, const QString &message, const bool warning)
442{
443 m_lastError = code;
444 if (warning) {
445 qCWarning(KCGROUPS_LOG) << message;
446 } else {
447 qCDebug(KCGROUPS_LOG) << "ERROR: " << message;
448 }
449 emit q_ptr->errorOccurred(m_lastError);
450}
A desktop application in a systemd transient scope.
OptionalQULongLong memoryMin
memory usage protection (in bytes) of all executed processes within the application.
QString instance
the app instance random identifier.
QString description
the systemd unit description.
OptionalQULongLong memoryHigh
throttling limit on memory usage (in bytes) of all executed processes within the application.
void ioWeightChanged(const OptionalQULongLong &weight)
emitted when the io weight has changed
KApplicationScope(const QString &path, QObject *parent=nullptr)
Use when only path is known.
OptionalQULongLong cpuQuotaPeriod
duration in micoseconds over which the CPU time quota is measured.
void memoryMaxChanged(const OptionalQULongLong &memoryMax)
emitted when memoryMax has changed
QString cgroup
file path of the control group in /sys/fs/cgroup @accessors cgroup() @notifySignal cgroupChanged()
OptionalQULongLong ioWeight
Overall block I/O weight.
QString desktopName
the .desktop application name.
void cpuQuotaPeriodChanged(const OptionalQULongLong &period)
emitted when the cpu quota period has changed
void setMemorySwapMax(const OptionalQULongLong &memorySwapMax)
set memorySwapMax
OptionalQULongLong cpuQuota
cpu quota for cpu controller, in microseconds per second (1000000 means 100%).
void setIoWeight(const OptionalQULongLong &weight)
set ioWeight
void setCpuQuotaPeriod(const OptionalQULongLong &period)
set cpuQuotaPeriod
void cpuWeightChanged(const OptionalQULongLong &weight)
emitted when the cpu weight has changed
QString path
the dbus path of the application.
void setMemoryHigh(const OptionalQULongLong &memoryHigh)
set memoryHigh
OptionalQULongLong memorySwapMax
absolute limit on swap usage (in bytes) of all executed processes within the application.
ErrorCode lastError
code of the last error that occurred (NoError if none) @accessors lastError() @notifySignal errorOccu...
OptionalQULongLong memoryMax
absolute limit on memory usage (in bytes) of all executed processes within the application.
QString desktopFilePath
the application .desktop file if available.
void setMemoryMax(const OptionalQULongLong &memoryMax)
set memoryMax
void setMemoryMin(const OptionalQULongLong &memoryMin)
set memoryMin
void setCpuQuota(const OptionalQULongLong &quota)
set cpuQuota
OptionalQULongLong memoryLow
best-effort memory usage protection (in bytes) of all executed processes within the application.
void cpuQuotaChanged(const OptionalQULongLong &quota)
emitted when the cpu quota has changed
void memoryHighChanged(const OptionalQULongLong &memoryHigh)
emitted when memoryHigh has changed
void memorySwapMaxChanged(const OptionalQULongLong &memorySwapMax)
emitted when memorySwapMax has changed
static KApplicationScope * fromPid(uint pid, QObject *parent=nullptr)
Use when only PID is known.
void memoryLowChanged(const OptionalQULongLong &memoryLow)
emitted when memoryLow has changed
void setCpuWeight(const OptionalQULongLong &weight)
set cpuWeight
void memoryMinChanged(const OptionalQULongLong &memoryMin)
emitted when memoryMin has changed
OptionalQULongLong cpuWeight
cpu time weight.
void setMemoryLow(const OptionalQULongLong &memoryLow)
set memoryLow
void stop()
Stops the application.
QString id
the systemd unit id.
ErrorCode
The types of errors that can occur.
@ CacheFillError
Initial loading of property values failed.
@ StopFailedError
Error during stop() operation.
@ SetFailedError
A property set operation failed.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString name(StandardAction id)
KGuiItem properties()
QDBusConnection sessionBus()
QString message() const const
void finished(QDBusPendingCallWatcher *self)
QVariant argumentAt(int index) const const
QDBusError error() const const
bool isError() const const
QString fileName() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QString chopped(qsizetype len) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool isNull() const const
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:49:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.