Merge lp:~thomas-voss/unity-mir/oom_adjust into lp:unity-mir

Proposed by Thomas Voß
Status: Merged
Approved by: Gerry Boland
Approved revision: 123
Merged at revision: 127
Proposed branch: lp:~thomas-voss/unity-mir/oom_adjust
Merge into: lp:unity-mir
Diff against target: 250 lines (+212/-0)
1 file modified
src/modules/Unity/Application/taskcontroller.cpp (+212/-0)
To merge this branch: bzr merge lp:~thomas-voss/unity-mir/oom_adjust
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Mir development team Pending
Review via email: mp+191123@code.launchpad.net

Commit message

Slightly refactor the OOM adjustments to be a pure implementation detail of the TaskManager.

Description of the change

Slightly refactor the OOM adjustments to be a pure implementation detail of the TaskManager.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

+struct OomAdj
+struct OomScoreAdj
Please don't use the "Adj" abbreviation, it's vague. I'd recommend OomAdjuster.

+ static int disableOomKillerValue()
+ {
+ return -17;
+ }
Why not just a const static int disableOomKillerValue = -17; Similarly for the other *Value() methods.

+ static int {min,max}Value()
I'd prefer a more descriptive name like "{least,most}LikelyToKillValue"

+ static QString filePattern()
+ {
+ return QString("/proc/%1/oom_adj");
+ }
Why this in separate public method? It's only used in applyForPid, and is internal detail.

+ throw std::runtime_error("OomScoreAdj created with invalid value.");
This exception is not caught anywhere, as we tend to avoid using exceptions in our Qt code.

+struct OomScoreAdj
+ static OomScoreAdj leastLikelyToBeKilled()
+ {
+ // We could be way more clever here if we knew the distribution
+ // of oom_score_adj values of all app processes. However, we just
+ // make sure that the process is not ignored by the oom killer for now.
+ return OomScoreAdj(minValue()+200);
+ }
I'd rather you move this comment beside minValue(), and have minValue=-800 for now.

focusCallback=....
+ DLOG("startCallback: Could not oom killer probability for process");
Copy/paste typo in the DLOG

TaskController::resume...
+ DLOG("TaskController::suspend: Could not oom killer probability for process");
Again, c/p typo

This is a lot of code to do something relatively simple. While I like the clear structuring and documentation, creating an object which just writes a set of values to a file, and a separate object for slightly different values (but with same meaning) to a different file, is overkill IMO.

A singleton class is enough to do this, which would internally handle both proc files and their respective values, and so would remove this complexity from TaskManager's code:

+ if (!updateOomScoreAdj(pid, OomScoreAdj::leastLikelyToBeKilled()))
+ {
+ if (!updateOomAdj(pid, OomAdj::leastLikelyToBeKilled()))
+ DLOG("startCallback: Could not oom killer probability for process");
+ }

reducing it to just:

if (!OomAdjuster::instance()->set(pid, OomAdjuster::leastLikelyToBeKilled)
    DLOG("startCallback: Could not set OOM killer probability for process");

review: Needs Fixing
Revision history for this message
Thomas Voß (thomas-voss) wrote :

> +struct OomAdj
> +struct OomScoreAdj
> Please don't use the "Adj" abbreviation, it's vague. I'd recommend
> OomAdjuster.
>
> + static int disableOomKillerValue()
> + {
> + return -17;
> + }
> Why not just a const static int disableOomKillerValue = -17; Similarly for the
> other *Value() methods.
>

As static constants are less likely to be hit by static initialization fiascos.

> + static int {min,max}Value()
> I'd prefer a more descriptive name like "{least,most}LikelyToKillValue"
>

Hmmm, I intentionally named them min and max, and introduced two more semantic functions to distinguish between what the kernel oom considers min and max and how it is interpreted, respectively.

> + static QString filePattern()
> + {
> + return QString("/proc/%1/oom_adj");
> + }
> Why this in separate public method? It's only used in applyForPid, and is
> internal detail.

yup, fair point.

>
> + throw std::runtime_error("OomScoreAdj created with invalid value.");
> This exception is not caught anywhere, as we tend to avoid using exceptions in
> our Qt code.
>
> +struct OomScoreAdj
> + static OomScoreAdj leastLikelyToBeKilled()
> + {
> + // We could be way more clever here if we knew the distribution
> + // of oom_score_adj values of all app processes. However, we just
> + // make sure that the process is not ignored by the oom killer for now.
> + return OomScoreAdj(minValue()+200);
> + }
> I'd rather you move this comment beside minValue(), and have minValue=-800 for
> now.
>

As explained before, minValue returns the value as defined in linux/oom.h and there is a significant difference between the min value and our interpretation of least likely to be killed.

> focusCallback=....
> + DLOG("startCallback: Could not oom killer probability for process");
> Copy/paste typo in the DLOG
>
> TaskController::resume...
> + DLOG("TaskController::suspend: Could not oom killer probability for
> process");
> Again, c/p typo
>
>
>
> This is a lot of code to do something relatively simple. While I like the
> clear structuring and documentation, creating an object which just writes a
> set of values to a file, and a separate object for slightly different values
> (but with same meaning) to a different file, is overkill IMO.
>
> A singleton class is enough to do this, which would internally handle both
> proc files and their respective values, and so would remove this complexity
> from TaskManager's code:
>

I think we should stop using an anti-pattern like a Singleton _now_. It leads to tight coupling and is increasingly difficult to test. That being said, I do agree that we can further condense updateOom{Score}Adj.

> + if (!updateOomScoreAdj(pid, OomScoreAdj::leastLikelyToBeKilled()))
> + {
> + if (!updateOomAdj(pid, OomAdj::leastLikelyToBeKilled()))
> + DLOG("startCallback: Could not oom killer probability for process");
> + }
>
> reducing it to just:
>
> if (!OomAdjuster::instance()->set(pid, OomAdjuster::leastLikelyToBeKilled)
> DLOG("startCallback: Could not set OOM killer probability for process");

lp:~thomas-voss/unity-mir/oom_adjust updated
122. By Thomas Voß

Address reviewer comments.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

> > Please don't use the "Adj" abbreviation, it's vague. I'd recommend OomAdjuster.
Please address.

> As static constants are less likely to be hit by static initialization fiascos.
I don't understand this, these are integers, not static classes that have some interdependency.

> > + throw std::runtime_error("OomScoreAdj created with invalid value.");
> > This exception is not caught anywhere, as we tend to avoid using exceptions
> > in our Qt code.
I badly phrased this, I wanted you to remove the exception entirely as Qt is not fully exception safe: https://qt-project.org/doc/qt-5.0/qtdoc/exceptionsafety.html and so we avoid their use.

lp:~thomas-voss/unity-mir/oom_adjust updated
123. By Thomas Voß

 * Rename Adj -> Adjuster as per review request.
 * Do not throw on construction from invalid value.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/Application/taskcontroller.cpp'
2--- src/modules/Unity/Application/taskcontroller.cpp 2013-09-26 15:36:27 +0000
3+++ src/modules/Unity/Application/taskcontroller.cpp 2013-10-15 16:19:00 +0000
4@@ -32,6 +32,210 @@
5 #include <signal.h>
6 #include <unistd.h>
7
8+// linux specific
9+#include <linux/oom.h>
10+
11+namespace
12+{
13+/**
14+ * From man proc:
15+ *
16+ * This file can be used to adjust the score used to select which
17+ * process should be killed in an out-of-memory (OOM) situation. The
18+ * kernel uses this value for a bit-shift opera‐ tion of the process's
19+ * oom_score value: valid values are in the range -16 to +15, plus the
20+ * special value -17, which disables OOM-killing altogether for this
21+ * process. A posi‐ tive score increases the likelihood of this process
22+ * being killed by the OOM-killer; a negative score decreases the
23+ * likelihood.
24+ *
25+ * The default value for this file is 0; a new process inherits its
26+ * parent's oom_adj setting. A process must be privileged
27+ * (CAP_SYS_RESOURCE) to update this file.
28+
29+ * Since Linux 2.6.36, use of this file is deprecated in favor of
30+ * /proc/[pid]/oom_score_adj.
31+ */
32+struct OomAdjuster
33+{
34+ static int disableOomKillerValue()
35+ {
36+ return OOM_DISABLE;
37+ }
38+
39+ static int minValue()
40+ {
41+ return OOM_ADJUST_MIN;
42+ }
43+
44+ static int maxValue()
45+ {
46+ return OOM_ADJUST_MAX;
47+ }
48+
49+ static OomAdjuster leastLikelyToBeKilled()
50+ {
51+ return OomAdjuster(minValue());
52+ }
53+
54+ static OomAdjuster mostLikelyToBeKilled()
55+ {
56+ return OomAdjuster(maxValue());
57+ }
58+
59+ OomAdjuster(int value) : value(value)
60+ {
61+ }
62+
63+ bool isValid() const
64+ {
65+ return !(value < disableOomKillerValue() || value > maxValue());
66+ }
67+
68+ bool applyForPid(pid_t pid) const
69+ {
70+ auto fn = QString("/proc/%1/oom_adj").arg(pid);
71+ QFile file(fn);
72+
73+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
74+ return false;
75+
76+ QTextStream out(&file);
77+ out << value;
78+
79+ return true;
80+ }
81+
82+ int value;
83+};
84+
85+/**
86+ * From man proc:
87+ *
88+ * This file can be used to adjust the badness heuristic used to
89+ * select which process gets killed in out-of-memory conditions.
90+ *
91+ * The badness heuristic assigns a value to each candidate
92+ * task ranging from 0 (never kill) to 1000 (always kill)
93+ * to determine which process is targeted. The units are
94+ * roughly a proportion along that range of allowed memory
95+ * the process may allocate from, based on an estimation of
96+ * its current memory and swap use. For example, if a task
97+ * is using all allowed memory, its badness score will be
98+ * 1000. If it is using half of its allowed memory, its
99+ * score will be 500.
100+ *
101+ * There is an additional factor included in the badness score: root
102+ * processes are given 3% extra memory over other tasks.
103+ *
104+ * The amount of "allowed" memory depends on the context in
105+ * which the OOM-killer was called. If it is due to the
106+ * memory assigned to the allocating task's cpuset being
107+ * exhausted, the allowed memory represents the set of mems
108+ * assigned to that cpuset (see cpuset(7)). If it is due
109+ * to a mempolicy's node(s) being exhausted, the allowed
110+ * memory represents the set of mempolicy nodes. If it is
111+ * due to a memory limit (or swap limit) being reached, the
112+ * allowed memory is that configured limit. Finally, if it
113+ * is due to the entire system being out of memory, the
114+ * allowed memory represents all allocatable resources.
115+ *
116+ * The value of oom_score_adj is added to the badness score before it
117+ * is used to determine which task to kill. Acceptable values range
118+ * from -1000 (OOM_SCORE_ADJ_MIN) to +1000 (OOM_SCORE_ADJ_MAX). This
119+ * allows user space to control the preference for OOM-killing,
120+ * ranging from always preferring a certain task or completely
121+ * disabling it from OOM- killing. The lowest possible value, -1000,
122+ * is equivalent to disabling OOM-killing entirely for that task,
123+ * since it will always report a badness score of 0.
124+ *
125+ * Consequently, it is very simple for user space to define the amount
126+ * of memory to consider for each task. Setting a oom_score_adj value
127+ * of +500, for example, is roughly equiv‐ alent to allowing the
128+ * remainder of tasks sharing the same system, cpuset, mempolicy, or
129+ * memory controller resources to use at least 50% more memory. A
130+ * value of -500, on the other hand, would be roughly equivalent to
131+ * discounting 50% of the task's allowed memory from being considered
132+ * as scoring against the task.
133+ *
134+ * For backward compatibility with previous kernels,
135+ * /proc/[pid]/oom_adj can still be used to tune the badness score.
136+ * Its value is scaled linearly with oom_score_adj.
137+ *
138+ * Writing to /proc/[pid]/oom_score_adj or
139+ * /proc/[pid]/oom_adj will change the other with its
140+ * scaled value.
141+ */
142+struct OomScoreAdjuster
143+{
144+ static int disableOomKillerValue()
145+ {
146+ return OOM_SCORE_ADJ_MIN;
147+ }
148+
149+ static int minValue()
150+ {
151+ return OOM_SCORE_ADJ_MIN;
152+ }
153+
154+ static int maxValue()
155+ {
156+ return OOM_SCORE_ADJ_MAX;
157+ }
158+
159+ static OomScoreAdjuster leastLikelyToBeKilled()
160+ {
161+ // We could be way more clever here if we knew the distribution
162+ // of oom_score_adj values of all app processes. However, we just
163+ // make sure that the process is not ignored by the oom killer for now.
164+ return OomScoreAdjuster(minValue()+200);
165+ }
166+
167+ static OomScoreAdjuster mostLikelyToBeKilled()
168+ {
169+ return OomScoreAdjuster(maxValue());
170+ }
171+
172+ OomScoreAdjuster(int value) : value(value)
173+ {
174+ }
175+
176+ bool isValid() const
177+ {
178+ return !(value < disableOomKillerValue() || value > maxValue());
179+ }
180+
181+ bool applyForPid(pid_t pid) const
182+ {
183+ auto fn = QString("/proc/%1/oom_score_adj").arg(pid);
184+ QFile file(fn);
185+
186+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
187+ return false;
188+
189+ QTextStream out(&file);
190+ out << value;
191+
192+ return true;
193+ }
194+
195+ int value;
196+};
197+
198+void ensureProcessIsUnlikelyToBeKilled(pid_t pid)
199+{
200+ if (!OomScoreAdjuster::leastLikelyToBeKilled().applyForPid(pid))
201+ if (!OomAdjuster::leastLikelyToBeKilled().applyForPid(pid))
202+ DLOG("ensureProcessIsUnlikelyToBeKilled failed");
203+}
204+
205+void ensureProcessIsLikelyToBeKilled(pid_t pid)
206+{
207+ if (!OomScoreAdjuster::mostLikelyToBeKilled().applyForPid(pid))
208+ if (!OomAdjuster::mostLikelyToBeKilled().applyForPid(pid))
209+ DLOG("ensureProcessIsLikelyToBeKilled failed");
210+}
211+}
212
213 TaskController* TaskController::m_theTaskController = nullptr;
214
215@@ -48,6 +252,8 @@
216 {
217 startCallback = [](const gchar * appId, gpointer userData) {
218 Q_UNUSED(userData)
219+ pid_t pid = upstart_app_launch_get_primary_pid(appId);
220+ ensureProcessIsUnlikelyToBeKilled(pid);
221 Q_EMIT TaskController::singleton()->processStartReport(QString(appId), false);
222 };
223
224@@ -58,6 +264,8 @@
225
226 focusCallback = [](const gchar * appId, gpointer userData) {
227 Q_UNUSED(userData)
228+ pid_t pid = upstart_app_launch_get_primary_pid(appId);
229+ ensureProcessIsUnlikelyToBeKilled(pid);
230 Q_EMIT TaskController::singleton()->requestFocus(QString(appId));
231 };
232
233@@ -137,6 +345,8 @@
234 DLOG("TaskController::suspend (this=%p, application=%p)", this, qPrintable(appId));
235 pid_t pid = upstart_app_launch_get_primary_pid(appId.toLatin1().constData());
236
237+ ensureProcessIsLikelyToBeKilled(pid);
238+
239 if (pid) {
240 kill(pid, SIGSTOP);
241 return true;
242@@ -150,6 +360,8 @@
243 DLOG("TaskController::resume (this=%p, application=%p)", this, qPrintable(appId));
244 pid_t pid = upstart_app_launch_get_primary_pid(appId.toLatin1().constData());
245
246+ ensureProcessIsUnlikelyToBeKilled(pid);
247+
248 if (pid) {
249 kill(pid, SIGCONT);
250 return true;

Subscribers

People subscribed via source and target branches