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 
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 }
bool isNull() const const
QString instance
the app instance random identifier.
void finished(QDBusPendingCallWatcher *self)
void setCpuQuotaPeriod(const OptionalQULongLong &period)
set cpuQuotaPeriod
QString id
the systemd unit id.
OptionalQULongLong cpuQuotaPeriod
duration in micoseconds over which the CPU time quota is measured.
void stop(Ekos::AlignState mode)
QString message() const const
T value() const const
QString description
the systemd unit description.
QString cgroup
file path of the control group in /sys/fs/cgroup @accessors cgroup() @notifySignal cgroupChanged()
QString chopped(int len) const const
void setMemoryLow(const OptionalQULongLong &memoryLow)
set memoryLow
ErrorCode
The types of errors that can occur.
bool isError() const const
KGuiItem properties()
void memorySwapMaxChanged(const OptionalQULongLong &memorySwapMax)
emitted when memorySwapMax has changed
void setIoWeight(const OptionalQULongLong &weight)
set ioWeight
@ StopFailedError
Error during stop() operation.
OptionalQULongLong memoryHigh
throttling limit on memory usage (in bytes) of all executed processes within the application.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
OptionalQULongLong memorySwapMax
absolute limit on swap usage (in bytes) of all executed processes within the application.
void ioWeightChanged(const OptionalQULongLong &weight)
emitted when the io weight has changed
void deleteLater()
A desktop application in a systemd transient scope.
QDBusConnection sessionBus()
Property
void memoryMinChanged(const OptionalQULongLong &memoryMin)
emitted when memoryMin has changed
void setMemoryMax(const OptionalQULongLong &memoryMax)
set memoryMax
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.
static KApplicationScope * fromPid(uint pid, QObject *parent=nullptr)
Use when only PID is known.
void stop()
Stops the application.
void memoryLowChanged(const OptionalQULongLong &memoryLow)
emitted when memoryLow has changed
KApplicationScope(const QString &path, QObject *parent=nullptr)
Use when only path is known.
void cpuWeightChanged(const OptionalQULongLong &weight)
emitted when the cpu weight has changed
OptionalQULongLong memoryMax
absolute limit on memory usage (in bytes) of all executed processes within the application.
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QString fileName() const const
QDBusError error() const const
OptionalQULongLong ioWeight
Overall block I/O weight.
void cpuQuotaPeriodChanged(const OptionalQULongLong &period)
emitted when the cpu quota period has changed
void setMemoryHigh(const OptionalQULongLong &memoryHigh)
set memoryHigh
void setCpuQuota(const OptionalQULongLong &quota)
set cpuQuota
void cpuQuotaChanged(const OptionalQULongLong &quota)
emitted when the cpu quota has changed
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
OptionalQULongLong cpuWeight
cpu time weight.
const char * name(StandardAction id)
QString desktopFilePath
the application .desktop file if available.
void setMemoryMin(const OptionalQULongLong &memoryMin)
set memoryMin
OptionalQULongLong cpuQuota
cpu quota for cpu controller, in microseconds per second (1000000 means 100%).
@ CacheFillError
Initial loading of property values failed.
QVariant argumentAt(int index) const const
QString path
the dbus path of the application.
@ SetFailedError
A property set operation failed.
QString desktopName
the .desktop application name.
void memoryMaxChanged(const OptionalQULongLong &memoryMax)
emitted when memoryMax has changed
void setMemorySwapMax(const OptionalQULongLong &memorySwapMax)
set memorySwapMax
QObject * parent() const const
QString message
void memoryHighChanged(const OptionalQULongLong &memoryHigh)
emitted when memoryHigh has changed
ErrorCode lastError
code of the last error that occurred (NoError if none) @accessors lastError() @notifySignal errorOccu...
void setCpuWeight(const OptionalQULongLong &weight)
set cpuWeight
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Dec 1 2023 04:13:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.