KCGroups

kapplicationscope.cpp
1 // SPDX-FileCopyrightText: 2020 Henri Chain <[email protected]>
2 // SPDX-FileCopyrightText: 2020 Kevin Ottens <[email protected]>
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 
12 static const Property<OptionalQULongLong> cpuQuotaProp = {&KApplicationScopePrivate::m_cpuQuota,
14  QStringLiteral("CPUQuotaPerSecUSec"),
15  true,
16  std::numeric_limits<qulonglong>::max()};
17 
18 static const Property<OptionalQULongLong> cpuQuotaPeriodProp = {&KApplicationScopePrivate::m_cpuQuotaPeriod,
20  QStringLiteral("CPUQuotaPeriodUSec"),
21  true,
22  std::numeric_limits<qulonglong>::max()};
23 
24 static const Property<OptionalQULongLong> cpuWeightProp = {&KApplicationScopePrivate::m_cpuWeight,
26  QStringLiteral("CPUWeight"),
27  true,
28  std::numeric_limits<qulonglong>::max()};
29 
30 static const Property<OptionalQULongLong> ioWeightProp = {&KApplicationScopePrivate::m_ioWeight,
32  QStringLiteral("IOWeight"),
33  true,
34  std::numeric_limits<qulonglong>::max()};
35 
36 static const Property<OptionalQULongLong> memoryLowProp = {&KApplicationScopePrivate::m_memoryLow,
38  QStringLiteral("MemoryLow"),
39  true,
40  std::numeric_limits<qulonglong>::max()};
41 
42 static const Property<OptionalQULongLong> memoryHighProp = {&KApplicationScopePrivate::m_memoryHigh,
44  QStringLiteral("MemoryHigh"),
45  true,
46  std::numeric_limits<qulonglong>::max()};
47 
48 static const Property<OptionalQULongLong> memoryMinProp = {&KApplicationScopePrivate::m_memoryMin,
50  QStringLiteral("MemoryMin"),
51  true,
52  std::numeric_limits<qulonglong>::max()};
53 
54 static const Property<OptionalQULongLong> memoryMaxProp = {&KApplicationScopePrivate::m_memoryMax,
56  QStringLiteral("MemoryMax"),
57  true,
58  std::numeric_limits<qulonglong>::max()};
59 
60 static const Property<OptionalQULongLong> memorySwapMaxProp = {&KApplicationScopePrivate::m_memorySwapMax,
62  QStringLiteral("MemorySwapMax"),
63  true,
64  std::numeric_limits<qulonglong>::max()};
65 
66 static 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 
85  : KApplicationScope::KApplicationScope(path, QString(), parent)
86 {
87 }
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 
129 OptionalQULongLong KApplicationScope::cpuQuota() const
130 {
131  return d_ptr->getProperty<OptionalQULongLong>(cpuQuotaProp);
132 }
133 
134 OptionalQULongLong KApplicationScope::cpuQuotaPeriod() const
135 {
136  return d_ptr->getProperty<OptionalQULongLong>(cpuQuotaPeriodProp);
137 }
138 
139 OptionalQULongLong KApplicationScope::cpuWeight() const
140 {
141  return d_ptr->getProperty<OptionalQULongLong>(cpuWeightProp);
142 }
143 
144 OptionalQULongLong KApplicationScope::ioWeight() const
145 {
146  return d_ptr->getProperty<OptionalQULongLong>(ioWeightProp);
147 }
148 
149 OptionalQULongLong KApplicationScope::memoryLow() const
150 {
151  return d_ptr->getProperty<OptionalQULongLong>(memoryLowProp);
152 }
153 
154 OptionalQULongLong KApplicationScope::memoryHigh() const
155 {
156  return d_ptr->getProperty<OptionalQULongLong>(memoryHighProp);
157 }
158 
159 OptionalQULongLong KApplicationScope::memoryMin() const
160 {
161  return d_ptr->getProperty<OptionalQULongLong>(memoryMinProp);
162 }
163 
164 OptionalQULongLong KApplicationScope::memoryMax() const
165 {
166  return d_ptr->getProperty<OptionalQULongLong>(memoryMaxProp);
167 }
168 
169 OptionalQULongLong KApplicationScope::memorySwapMax() const
170 {
171  return d_ptr->getProperty<OptionalQULongLong>(memorySwapMaxProp);
172 }
173 
174 void KApplicationScope::setCpuQuota(const OptionalQULongLong &quota)
175 {
176  d_ptr->trySetProperty<OptionalQULongLong>(cpuQuotaProp, quota);
177 }
178 
179 void KApplicationScope::setCpuQuotaPeriod(const OptionalQULongLong &period)
180 {
181  d_ptr->trySetProperty<OptionalQULongLong>(cpuQuotaPeriodProp, period);
182 }
183 
184 void KApplicationScope::setCpuWeight(const OptionalQULongLong &weight)
185 {
186  d_ptr->trySetProperty<OptionalQULongLong>(cpuWeightProp, weight);
187 }
188 
189 void KApplicationScope::setIoWeight(const OptionalQULongLong &weight)
190 {
191  d_ptr->trySetProperty<OptionalQULongLong>(ioWeightProp, weight);
192 }
193 
194 void KApplicationScope::setMemoryLow(const OptionalQULongLong &memoryLow)
195 {
196  d_ptr->trySetProperty<OptionalQULongLong>(memoryLowProp, memoryLow);
197 }
198 
199 void KApplicationScope::setMemoryHigh(const OptionalQULongLong &memoryHigh)
200 {
201  d_ptr->trySetProperty<OptionalQULongLong>(memoryHighProp, memoryHigh);
202 }
203 
204 void KApplicationScope::setMemoryMin(const OptionalQULongLong &memoryMin)
205 {
206  d_ptr->trySetProperty<OptionalQULongLong>(memoryMinProp, memoryMin);
207 }
208 
209 void KApplicationScope::setMemoryMax(const OptionalQULongLong &memoryMax)
210 {
211  d_ptr->trySetProperty<OptionalQULongLong>(memoryMaxProp, memoryMax);
212 }
213 
214 void KApplicationScope::setMemorySwapMax(const OptionalQULongLong &memorySwapMax)
215 {
216  d_ptr->trySetProperty<OptionalQULongLong>(memorySwapMaxProp, memorySwapMax);
217 }
218 
220 {
221  d_ptr->stop();
222 }
223 
224 KApplicationScope::~KApplicationScope()
225 {
226  delete d_ptr;
227 }
228 
229 static const auto systemd1 = QStringLiteral("org.freedesktop.systemd1");
230 static const auto systemd1Scope = QStringLiteral("org.freedesktop.systemd1.Scope");
231 static const auto systemd1Service = QStringLiteral("org.freedesktop.systemd1.Service");
232 static const auto systemd1Slice = QStringLiteral("org.freedesktop.systemd1.Slice");
233 static const auto systemd1Unit = QStringLiteral("org.freedesktop.systemd1.Unit");
234 static 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 
250 KApplicationScopePrivate::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 
277 static const QRegularExpression appPattern(QStringLiteral("^apps?-(.+?)(?:-([^-]+))?\\.(scope|service|slice)$"));
278 
279 void 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 
301 template<typename T>
302 T 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 
312 template<typename T>
313 QVariant 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 
319 template<typename T>
320 T KApplicationScopePrivate::getProperty(const Property<T> &prop)
321 {
322  return this->*prop.privateMember;
323 }
324 
325 template<typename T>
326 void 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 
333 template<typename T>
334 void 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 
344 template<typename T>
345 void 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 
357 void KApplicationScopePrivate::stop()
358 {
359  const auto *watcher = new QDBusPendingCallWatcher(m_unit->Stop(QStringLiteral("replace")), q_ptr);
361  handleVoidCallFinished(watcher, KApplicationScope::StopFailedError);
362  });
363 }
364 
365 void 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 
376 void KApplicationScopePrivate::handleGetAllCallFinished(QDBusPendingCallWatcher *call)
377 {
378  QDBusPendingReply<QVariantMap> reply = *call;
379  if (reply.isError()) {
380  setError(KApplicationScope::CacheFillError, reply.error().message());
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 
399 void KApplicationScopePrivate::handleGetUnitCallFinished(QDBusPendingCallWatcher *call)
400 {
401  QDBusPendingReply<QVariantMap> reply = *call;
402  if (reply.isError()) {
403  setError(KApplicationScope::CacheFillError, reply.error().message());
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 
441 void 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 }
void memoryHighChanged(const OptionalQULongLong &memoryHigh)
emitted when memoryHigh has changed
Initial loading of property values failed.
QString id
the systemd unit id.
void setCpuWeight(const OptionalQULongLong &weight)
set cpuWeight
Error during stop() operation.
OptionalQULongLong cpuQuotaPeriod
duration in micoseconds over which the CPU time quota is measured.
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
ErrorCode
The types of errors that can occur.
void setCpuQuotaPeriod(const OptionalQULongLong &period)
set cpuQuotaPeriod
QRegularExpressionMatchIterator globalMatch(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QString cgroup
file path of the control group in /sys/fs/cgroup cgroup() cgroupChanged()
OptionalQULongLong memoryHigh
throttling limit on memory usage (in bytes) of all executed processes within the application.
OptionalQULongLong memorySwapMax
absolute limit on swap usage (in bytes) of all executed processes within the application.
void finished(QDBusPendingCallWatcher *self)
QString message() const const
T value() const const
void setIoWeight(const OptionalQULongLong &weight)
set ioWeight
QString description
the systemd unit description.
QDBusConnection sessionBus()
void setMemoryLow(const OptionalQULongLong &memoryLow)
set memoryLow
void setMemoryMax(const OptionalQULongLong &memoryMax)
set memoryMax
A desktop application in a systemd transient scope.
void memorySwapMaxChanged(const OptionalQULongLong &memorySwapMax)
emitted when memorySwapMax has changed
static KApplicationScope * fromPid(uint pid, QObject *parent=nullptr)
Use when only PID is known.
QString chopped(int len) const const
OptionalQULongLong memoryMin
memory usage protection (in bytes) of all executed processes within the application.
OptionalQULongLong memoryLow
best-effort memory usage protection (in bytes) of all executed processes within the application...
bool isNull() const const
QString fileName() const const
OptionalQULongLong memoryMax
absolute limit on memory usage (in bytes) of all executed processes within the application.
void memoryLowChanged(const OptionalQULongLong &memoryLow)
emitted when memoryLow has changed
void ioWeightChanged(const OptionalQULongLong &weight)
emitted when the io weight has changed
void cpuWeightChanged(const OptionalQULongLong &weight)
emitted when the cpu weight has changed
void deleteLater()
QDBusError error() const const
KApplicationScope(const QString &path, QObject *parent=nullptr)
Use when only path is known.
void memoryMinChanged(const OptionalQULongLong &memoryMin)
emitted when memoryMin has changed
A property set operation failed.
Property
void stop()
Stops the application.
QString & replace(int position, int n, QChar after)
void setCpuQuota(const OptionalQULongLong &quota)
set cpuQuota
OptionalQULongLong ioWeight
Overall block I/O weight.
void cpuQuotaPeriodChanged(const OptionalQULongLong &period)
emitted when the cpu quota period has changed
QVariant argumentAt(int index) const const
void memoryMaxChanged(const OptionalQULongLong &memoryMax)
emitted when memoryMax has changed
void setMemoryMin(const OptionalQULongLong &memoryMin)
set memoryMin
OptionalQULongLong cpuQuota
cpu quota for cpu controller, in microseconds per second (1000000 means 100%).
QString path
the dbus path of the application.
QString desktopFilePath
the application .desktop file if available.
bool contains(const Key &key) const const
void setMemoryHigh(const OptionalQULongLong &memoryHigh)
set memoryHigh
void cpuQuotaChanged(const OptionalQULongLong &quota)
emitted when the cpu quota has changed
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
OptionalQULongLong cpuWeight
cpu time weight.
ErrorCode lastError
code of the last error that occurred (NoError if none) lastError() errorOccurred() ...
QString toString() const const
QString desktopName
the .desktop application name.
QString instance
the app instance random identifier.
bool isError() const const
void setMemorySwapMax(const OptionalQULongLong &memorySwapMax)
set memorySwapMax
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Oct 24 2021 23:04:01 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.