1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <sys/xattr.h>
4 #include <unistd.h>
5 
6 #include "fd-util.h"
7 #include "fileio.h"
8 #include "format-util.h"
9 #include "oomd-util.h"
10 #include "parse-util.h"
11 #include "path-util.h"
12 #include "procfs-util.h"
13 #include "signal-util.h"
14 #include "sort-util.h"
15 #include "stat-util.h"
16 #include "stdio-util.h"
17 
18 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
19                 oomd_cgroup_ctx_hash_ops,
20                 char,
21                 string_hash_func,
22                 string_compare_func,
23                 OomdCGroupContext,
24                 oomd_cgroup_context_free);
25 
log_kill(pid_t pid,int sig,void * userdata)26 static int log_kill(pid_t pid, int sig, void *userdata) {
27         log_debug("oomd attempting to kill " PID_FMT " with %s", pid, signal_to_string(sig));
28         return 0;
29 }
30 
increment_oomd_xattr(const char * path,const char * xattr,uint64_t num_procs_killed)31 static int increment_oomd_xattr(const char *path, const char *xattr, uint64_t num_procs_killed) {
32         _cleanup_free_ char *value = NULL;
33         char buf[DECIMAL_STR_MAX(uint64_t) + 1];
34         uint64_t curr_count = 0;
35         int r;
36 
37         assert(path);
38         assert(xattr);
39 
40         r = cg_get_xattr_malloc(SYSTEMD_CGROUP_CONTROLLER, path, xattr, &value);
41         if (r < 0 && r != -ENODATA)
42                 return r;
43 
44         if (!isempty(value)) {
45                  r = safe_atou64(value, &curr_count);
46                  if (r < 0)
47                          return r;
48         }
49 
50         if (curr_count > UINT64_MAX - num_procs_killed)
51                 return -EOVERFLOW;
52 
53         xsprintf(buf, "%"PRIu64, curr_count + num_procs_killed);
54         r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, path, xattr, buf, strlen(buf), 0);
55         if (r < 0)
56                 return r;
57 
58         return 0;
59 }
60 
oomd_cgroup_context_free(OomdCGroupContext * ctx)61 OomdCGroupContext *oomd_cgroup_context_free(OomdCGroupContext *ctx) {
62         if (!ctx)
63                 return NULL;
64 
65         free(ctx->path);
66         return mfree(ctx);
67 }
68 
oomd_pressure_above(Hashmap * h,usec_t duration,Set ** ret)69 int oomd_pressure_above(Hashmap *h, usec_t duration, Set **ret) {
70         _cleanup_set_free_ Set *targets = NULL;
71         OomdCGroupContext *ctx;
72         char *key;
73         int r;
74 
75         assert(h);
76         assert(ret);
77 
78         targets = set_new(NULL);
79         if (!targets)
80                 return -ENOMEM;
81 
82         HASHMAP_FOREACH_KEY(ctx, key, h) {
83                 if (ctx->memory_pressure.avg10 > ctx->mem_pressure_limit) {
84                         usec_t diff;
85 
86                         if (ctx->mem_pressure_limit_hit_start == 0)
87                                 ctx->mem_pressure_limit_hit_start = now(CLOCK_MONOTONIC);
88 
89                         diff = now(CLOCK_MONOTONIC) - ctx->mem_pressure_limit_hit_start;
90                         if (diff >= duration) {
91                                 r = set_put(targets, ctx);
92                                 if (r < 0)
93                                         return -ENOMEM;
94                         }
95                 } else
96                         ctx->mem_pressure_limit_hit_start = 0;
97         }
98 
99         if (!set_isempty(targets)) {
100                 *ret = TAKE_PTR(targets);
101                 return 1;
102         }
103 
104         *ret = NULL;
105         return 0;
106 }
107 
oomd_pgscan_rate(const OomdCGroupContext * c)108 uint64_t oomd_pgscan_rate(const OomdCGroupContext *c) {
109         uint64_t last_pgscan;
110 
111         assert(c);
112 
113         /* If last_pgscan > pgscan, assume the cgroup was recreated and reset last_pgscan to zero.
114          * pgscan is monotonic and in practice should not decrease (except in the recreation case). */
115         last_pgscan = c->last_pgscan;
116         if (c->last_pgscan > c->pgscan) {
117                 log_debug("Last pgscan %"PRIu64" greater than current pgscan %"PRIu64" for %s. Using last pgscan of zero.",
118                                 c->last_pgscan, c->pgscan, c->path);
119                 last_pgscan = 0;
120         }
121 
122         return c->pgscan - last_pgscan;
123 }
124 
oomd_mem_available_below(const OomdSystemContext * ctx,int threshold_permyriad)125 bool oomd_mem_available_below(const OomdSystemContext *ctx, int threshold_permyriad) {
126         uint64_t mem_threshold;
127 
128         assert(ctx);
129         assert(threshold_permyriad <= 10000);
130 
131         mem_threshold = ctx->mem_total * threshold_permyriad / (uint64_t) 10000;
132         return LESS_BY(ctx->mem_total, ctx->mem_used) < mem_threshold;
133 }
134 
oomd_swap_free_below(const OomdSystemContext * ctx,int threshold_permyriad)135 bool oomd_swap_free_below(const OomdSystemContext *ctx, int threshold_permyriad) {
136         uint64_t swap_threshold;
137 
138         assert(ctx);
139         assert(threshold_permyriad <= 10000);
140 
141         swap_threshold = ctx->swap_total * threshold_permyriad / (uint64_t) 10000;
142         return (ctx->swap_total - ctx->swap_used) < swap_threshold;
143 }
144 
oomd_sort_cgroup_contexts(Hashmap * h,oomd_compare_t compare_func,const char * prefix,OomdCGroupContext *** ret)145 int oomd_sort_cgroup_contexts(Hashmap *h, oomd_compare_t compare_func, const char *prefix, OomdCGroupContext ***ret) {
146         _cleanup_free_ OomdCGroupContext **sorted = NULL;
147         OomdCGroupContext *item;
148         size_t k = 0;
149 
150         assert(h);
151         assert(compare_func);
152         assert(ret);
153 
154         sorted = new0(OomdCGroupContext*, hashmap_size(h));
155         if (!sorted)
156                 return -ENOMEM;
157 
158         HASHMAP_FOREACH(item, h) {
159                 /* Skip over cgroups that are not valid candidates or are explicitly marked for omission */
160                 if ((item->path && prefix && !path_startswith(item->path, prefix)) || item->preference == MANAGED_OOM_PREFERENCE_OMIT)
161                         continue;
162 
163                 sorted[k++] = item;
164         }
165 
166         typesafe_qsort(sorted, k, compare_func);
167 
168         *ret = TAKE_PTR(sorted);
169 
170         assert(k <= INT_MAX);
171         return (int) k;
172 }
173 
oomd_cgroup_kill(const char * path,bool recurse,bool dry_run)174 int oomd_cgroup_kill(const char *path, bool recurse, bool dry_run) {
175         _cleanup_set_free_ Set *pids_killed = NULL;
176         int r;
177 
178         assert(path);
179 
180         if (dry_run) {
181                 _cleanup_free_ char *cg_path = NULL;
182 
183                 r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &cg_path);
184                 if (r < 0)
185                         return r;
186 
187                 log_debug("oomd dry-run: Would have tried to kill %s with recurse=%s", cg_path, true_false(recurse));
188                 return 0;
189         }
190 
191         pids_killed = set_new(NULL);
192         if (!pids_killed)
193                 return -ENOMEM;
194 
195         r = increment_oomd_xattr(path, "user.oomd_ooms", 1);
196         if (r < 0)
197                 log_debug_errno(r, "Failed to set user.oomd_ooms before kill: %m");
198 
199         if (recurse)
200                 r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, path, SIGKILL, CGROUP_IGNORE_SELF, pids_killed, log_kill, NULL);
201         else
202                 r = cg_kill(SYSTEMD_CGROUP_CONTROLLER, path, SIGKILL, CGROUP_IGNORE_SELF, pids_killed, log_kill, NULL);
203 
204         /* The cgroup could have been cleaned up after we have sent SIGKILL to all of the processes, but before
205          * we could do one last iteration of cgroup.procs to check. Or the service unit could have exited and
206          * was removed between picking candidates and coming into this function. In either case, let's log
207          * about it let the caller decide what to do once they know how many PIDs were killed. */
208         if (IN_SET(r, -ENOENT, -ENODEV))
209                 log_debug_errno(r, "Error when sending SIGKILL to processes in cgroup path %s, ignoring: %m", path);
210         else if (r < 0)
211                 return r;
212 
213         if (set_isempty(pids_killed))
214                 log_debug("Nothing killed when attempting to kill %s", path);
215 
216         r = increment_oomd_xattr(path, "user.oomd_kill", set_size(pids_killed));
217         if (r < 0)
218                 log_debug_errno(r, "Failed to set user.oomd_kill on kill: %m");
219 
220         return set_size(pids_killed) != 0;
221 }
222 
223 typedef void (*dump_candidate_func)(const OomdCGroupContext *ctx, FILE *f, const char *prefix);
224 
dump_kill_candidates(OomdCGroupContext ** sorted,int n,int dump_until,dump_candidate_func dump_func)225 static int dump_kill_candidates(OomdCGroupContext **sorted, int n, int dump_until, dump_candidate_func dump_func) {
226         /* Try dumping top offendors, ignoring any errors that might happen. */
227         _cleanup_free_ char *dump = NULL;
228         _cleanup_fclose_ FILE *f = NULL;
229         int r;
230         size_t size;
231 
232         f = open_memstream_unlocked(&dump, &size);
233         if (!f)
234                 return -errno;
235 
236         fprintf(f, "Considered %d cgroups for killing, top candidates were:\n", n);
237         for (int i = 0; i < dump_until; i++)
238                 dump_func(sorted[i], f, "\t");
239 
240         r = fflush_and_check(f);
241         if (r < 0)
242                 return r;
243 
244         return log_dump(LOG_INFO, dump);
245 }
246 
oomd_kill_by_pgscan_rate(Hashmap * h,const char * prefix,bool dry_run,char ** ret_selected)247 int oomd_kill_by_pgscan_rate(Hashmap *h, const char *prefix, bool dry_run, char **ret_selected) {
248         _cleanup_free_ OomdCGroupContext **sorted = NULL;
249         int n, r, ret = 0;
250         int dump_until;
251 
252         assert(h);
253         assert(ret_selected);
254 
255         n = oomd_sort_cgroup_contexts(h, compare_pgscan_rate_and_memory_usage, prefix, &sorted);
256         if (n < 0)
257                 return n;
258 
259         dump_until = MIN(n, DUMP_ON_KILL_COUNT);
260         for (int i = 0; i < n; i++) {
261                 /* Skip cgroups with no reclaim and memory usage; it won't alleviate pressure.
262                  * Continue since there might be "avoid" cgroups at the end. */
263                 if (sorted[i]->pgscan == 0 && sorted[i]->current_memory_usage == 0)
264                         continue;
265 
266                 r = oomd_cgroup_kill(sorted[i]->path, true, dry_run);
267                 if (r == -ENOMEM)
268                         return r; /* Treat oom as a hard error */
269                 if (r < 0) {
270                         if (ret == 0)
271                                 ret = r;
272                         continue; /* Try to find something else to kill */
273                 }
274 
275                 dump_until = MAX(dump_until, i);
276                 char *selected = strdup(sorted[i]->path);
277                 if (!selected)
278                         return -ENOMEM;
279                 *ret_selected = selected;
280                 ret = r;
281                 break;
282         }
283 
284         dump_kill_candidates(sorted, n, dump_until, oomd_dump_memory_pressure_cgroup_context);
285 
286         return ret;
287 }
288 
oomd_kill_by_swap_usage(Hashmap * h,uint64_t threshold_usage,bool dry_run,char ** ret_selected)289 int oomd_kill_by_swap_usage(Hashmap *h, uint64_t threshold_usage, bool dry_run, char **ret_selected) {
290         _cleanup_free_ OomdCGroupContext **sorted = NULL;
291         int n, r, ret = 0;
292         int dump_until;
293 
294         assert(h);
295         assert(ret_selected);
296 
297         n = oomd_sort_cgroup_contexts(h, compare_swap_usage, NULL, &sorted);
298         if (n < 0)
299                 return n;
300 
301         dump_until = MIN(n, DUMP_ON_KILL_COUNT);
302         /* Try to kill cgroups with non-zero swap usage until we either succeed in killing or we get to a cgroup with
303          * no swap usage. Threshold killing only cgroups with more than threshold swap usage. */
304         for (int i = 0; i < n; i++) {
305                 /* Skip over cgroups with not enough swap usage. Don't break since there might be "avoid"
306                  * cgroups at the end. */
307                 if (sorted[i]->swap_usage <= threshold_usage)
308                         continue;
309 
310                 r = oomd_cgroup_kill(sorted[i]->path, true, dry_run);
311                 if (r == -ENOMEM)
312                         return r; /* Treat oom as a hard error */
313                 if (r < 0) {
314                         if (ret == 0)
315                                 ret = r;
316                         continue; /* Try to find something else to kill */
317                 }
318 
319                 dump_until = MAX(dump_until, i);
320                 char *selected = strdup(sorted[i]->path);
321                 if (!selected)
322                         return -ENOMEM;
323                 *ret_selected = selected;
324                 ret = r;
325                 break;
326         }
327 
328         dump_kill_candidates(sorted, n, dump_until, oomd_dump_swap_cgroup_context);
329 
330         return ret;
331 }
332 
oomd_cgroup_context_acquire(const char * path,OomdCGroupContext ** ret)333 int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) {
334         _cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *ctx = NULL;
335         _cleanup_free_ char *p = NULL, *val = NULL;
336         bool is_root;
337         uid_t uid;
338         int r;
339 
340         assert(path);
341         assert(ret);
342 
343         ctx = new0(OomdCGroupContext, 1);
344         if (!ctx)
345                 return -ENOMEM;
346 
347         is_root = empty_or_root(path);
348         ctx->preference = MANAGED_OOM_PREFERENCE_NONE;
349 
350         r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, "memory.pressure", &p);
351         if (r < 0)
352                 return log_debug_errno(r, "Error getting cgroup memory pressure path from %s: %m", path);
353 
354         r = read_resource_pressure(p, PRESSURE_TYPE_FULL, &ctx->memory_pressure);
355         if (r < 0)
356                 return log_debug_errno(r, "Error parsing memory pressure from %s: %m", p);
357 
358         r = cg_get_owner(SYSTEMD_CGROUP_CONTROLLER, path, &uid);
359         if (r < 0)
360                 log_debug_errno(r, "Failed to get owner/group from %s: %m", path);
361         else if (uid == 0) {
362                 /* Ignore most errors when reading the xattr since it is usually unset and cgroup xattrs are only used
363                  * as an optional feature of systemd-oomd (and the system might not even support them). */
364                 r = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, path, "user.oomd_avoid");
365                 if (r == -ENOMEM)
366                         return r;
367                 ctx->preference = r == 1 ? MANAGED_OOM_PREFERENCE_AVOID : ctx->preference;
368 
369                 r = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, path, "user.oomd_omit");
370                 if (r == -ENOMEM)
371                         return r;
372                 ctx->preference = r == 1 ? MANAGED_OOM_PREFERENCE_OMIT : ctx->preference;
373         }
374 
375         if (is_root) {
376                 r = procfs_memory_get_used(&ctx->current_memory_usage);
377                 if (r < 0)
378                         return log_debug_errno(r, "Error getting memory used from procfs: %m");
379         } else {
380                 r = cg_get_attribute_as_uint64(SYSTEMD_CGROUP_CONTROLLER, path, "memory.current", &ctx->current_memory_usage);
381                 if (r < 0)
382                         return log_debug_errno(r, "Error getting memory.current from %s: %m", path);
383 
384                 r = cg_get_attribute_as_uint64(SYSTEMD_CGROUP_CONTROLLER, path, "memory.min", &ctx->memory_min);
385                 if (r < 0)
386                         return log_debug_errno(r, "Error getting memory.min from %s: %m", path);
387 
388                 r = cg_get_attribute_as_uint64(SYSTEMD_CGROUP_CONTROLLER, path, "memory.low", &ctx->memory_low);
389                 if (r < 0)
390                         return log_debug_errno(r, "Error getting memory.low from %s: %m", path);
391 
392                 r = cg_get_attribute_as_uint64(SYSTEMD_CGROUP_CONTROLLER, path, "memory.swap.current", &ctx->swap_usage);
393                 if (r == -ENODATA)
394                         /* The kernel can be compiled without support for memory.swap.* files,
395                          * or it can be disabled with boot param 'swapaccount=0' */
396                         log_once(LOG_WARNING, "No kernel support for memory.swap.current from %s (try boot param swapaccount=1), ignoring.", path);
397                 else if (r < 0)
398                         return log_debug_errno(r, "Error getting memory.swap.current from %s: %m", path);
399 
400                 r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, path, "memory.stat", STRV_MAKE("pgscan"), &val);
401                 if (r < 0)
402                         return log_debug_errno(r, "Error getting pgscan from memory.stat under %s: %m", path);
403 
404                 r = safe_atou64(val, &ctx->pgscan);
405                 if (r < 0)
406                         return log_debug_errno(r, "Error converting pgscan value to uint64_t: %m");
407         }
408 
409         ctx->path = strdup(empty_to_root(path));
410         if (!ctx->path)
411                 return -ENOMEM;
412 
413         *ret = TAKE_PTR(ctx);
414         return 0;
415 }
416 
oomd_system_context_acquire(const char * proc_meminfo_path,OomdSystemContext * ret)417 int oomd_system_context_acquire(const char *proc_meminfo_path, OomdSystemContext *ret) {
418         _cleanup_fclose_ FILE *f = NULL;
419         unsigned field_filled = 0;
420         OomdSystemContext ctx = {};
421         uint64_t mem_available, swap_free;
422         int r;
423 
424         enum {
425                 MEM_TOTAL = 1U << 0,
426                 MEM_AVAILABLE = 1U << 1,
427                 SWAP_TOTAL = 1U << 2,
428                 SWAP_FREE = 1U << 3,
429                 ALL = MEM_TOTAL|MEM_AVAILABLE|SWAP_TOTAL|SWAP_FREE,
430         };
431 
432         assert(proc_meminfo_path);
433         assert(ret);
434 
435         f = fopen(proc_meminfo_path, "re");
436         if (!f)
437                 return -errno;
438 
439         for (;;) {
440                 _cleanup_free_ char *line = NULL;
441                 char *word;
442 
443                 r = read_line(f, LONG_LINE_MAX, &line);
444                 if (r < 0)
445                         return r;
446                 if (r == 0)
447                         return -EINVAL;
448 
449                 if ((word = startswith(line, "MemTotal:"))) {
450                         field_filled |= MEM_TOTAL;
451                         r = convert_meminfo_value_to_uint64_bytes(word, &ctx.mem_total);
452                 } else if ((word = startswith(line, "MemAvailable:"))) {
453                         field_filled |= MEM_AVAILABLE;
454                         r = convert_meminfo_value_to_uint64_bytes(word, &mem_available);
455                 } else if ((word = startswith(line, "SwapTotal:"))) {
456                         field_filled |= SWAP_TOTAL;
457                         r = convert_meminfo_value_to_uint64_bytes(word, &ctx.swap_total);
458                 } else if ((word = startswith(line, "SwapFree:"))) {
459                         field_filled |= SWAP_FREE;
460                         r = convert_meminfo_value_to_uint64_bytes(word, &swap_free);
461                 } else
462                         continue;
463 
464                 if (r < 0)
465                         return log_debug_errno(r, "Error converting '%s' from %s to uint64_t: %m", line, proc_meminfo_path);
466 
467                 if (field_filled == ALL)
468                         break;
469         }
470 
471         if (field_filled != ALL)
472                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "%s is missing expected fields", proc_meminfo_path);
473 
474         if (mem_available > ctx.mem_total)
475                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
476                                        "MemAvailable (%" PRIu64 ") cannot be greater than MemTotal (%" PRIu64 ") %m",
477                                        mem_available,
478                                        ctx.mem_total);
479 
480         if (swap_free > ctx.swap_total)
481                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
482                                        "SwapFree (%" PRIu64 ") cannot be greater than SwapTotal (%" PRIu64 ") %m",
483                                        swap_free,
484                                        ctx.swap_total);
485 
486         ctx.mem_used = ctx.mem_total - mem_available;
487         ctx.swap_used = ctx.swap_total - swap_free;
488 
489         *ret = ctx;
490         return 0;
491 }
492 
oomd_insert_cgroup_context(Hashmap * old_h,Hashmap * new_h,const char * path)493 int oomd_insert_cgroup_context(Hashmap *old_h, Hashmap *new_h, const char *path) {
494         _cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *curr_ctx = NULL;
495         OomdCGroupContext *old_ctx;
496         int r;
497 
498         assert(new_h);
499         assert(path);
500 
501         path = empty_to_root(path);
502 
503         r = oomd_cgroup_context_acquire(path, &curr_ctx);
504         if (r < 0)
505                 return log_debug_errno(r, "Failed to get OomdCGroupContext for %s: %m", path);
506 
507         assert_se(streq(path, curr_ctx->path));
508 
509         old_ctx = hashmap_get(old_h, path);
510         if (old_ctx) {
511                 curr_ctx->last_pgscan = old_ctx->pgscan;
512                 curr_ctx->mem_pressure_limit = old_ctx->mem_pressure_limit;
513                 curr_ctx->mem_pressure_limit_hit_start = old_ctx->mem_pressure_limit_hit_start;
514                 curr_ctx->last_had_mem_reclaim = old_ctx->last_had_mem_reclaim;
515         }
516 
517         if (oomd_pgscan_rate(curr_ctx) > 0)
518                 curr_ctx->last_had_mem_reclaim = now(CLOCK_MONOTONIC);
519 
520         r = hashmap_put(new_h, curr_ctx->path, curr_ctx);
521         if (r < 0)
522                 return r;
523 
524         TAKE_PTR(curr_ctx);
525         return 0;
526 }
527 
oomd_update_cgroup_contexts_between_hashmaps(Hashmap * old_h,Hashmap * curr_h)528 void oomd_update_cgroup_contexts_between_hashmaps(Hashmap *old_h, Hashmap *curr_h) {
529         OomdCGroupContext *ctx;
530 
531         assert(old_h);
532         assert(curr_h);
533 
534         HASHMAP_FOREACH(ctx, curr_h) {
535                 OomdCGroupContext *old_ctx;
536 
537                 old_ctx = hashmap_get(old_h, ctx->path);
538                 if (!old_ctx)
539                         continue;
540 
541                 ctx->last_pgscan = old_ctx->pgscan;
542                 ctx->mem_pressure_limit = old_ctx->mem_pressure_limit;
543                 ctx->mem_pressure_limit_hit_start = old_ctx->mem_pressure_limit_hit_start;
544                 ctx->last_had_mem_reclaim = old_ctx->last_had_mem_reclaim;
545 
546                 if (oomd_pgscan_rate(ctx) > 0)
547                         ctx->last_had_mem_reclaim = now(CLOCK_MONOTONIC);
548         }
549 }
550 
oomd_dump_swap_cgroup_context(const OomdCGroupContext * ctx,FILE * f,const char * prefix)551 void oomd_dump_swap_cgroup_context(const OomdCGroupContext *ctx, FILE *f, const char *prefix) {
552         assert(ctx);
553         assert(f);
554 
555         if (!empty_or_root(ctx->path))
556                 fprintf(f,
557                         "%sPath: %s\n"
558                         "%s\tSwap Usage: %s\n",
559                         strempty(prefix), ctx->path,
560                         strempty(prefix), FORMAT_BYTES(ctx->swap_usage));
561         else
562                 fprintf(f,
563                         "%sPath: %s\n"
564                         "%s\tSwap Usage: (see System Context)\n",
565                         strempty(prefix), ctx->path,
566                         strempty(prefix));
567 }
568 
oomd_dump_memory_pressure_cgroup_context(const OomdCGroupContext * ctx,FILE * f,const char * prefix)569 void oomd_dump_memory_pressure_cgroup_context(const OomdCGroupContext *ctx, FILE *f, const char *prefix) {
570         assert(ctx);
571         assert(f);
572 
573         fprintf(f,
574                 "%sPath: %s\n"
575                 "%s\tMemory Pressure Limit: %lu.%02lu%%\n"
576                 "%s\tPressure: Avg10: %lu.%02lu Avg60: %lu.%02lu Avg300: %lu.%02lu Total: %s\n"
577                 "%s\tCurrent Memory Usage: %s\n",
578                 strempty(prefix), ctx->path,
579                 strempty(prefix), LOADAVG_INT_SIDE(ctx->mem_pressure_limit), LOADAVG_DECIMAL_SIDE(ctx->mem_pressure_limit),
580                 strempty(prefix),
581                 LOADAVG_INT_SIDE(ctx->memory_pressure.avg10), LOADAVG_DECIMAL_SIDE(ctx->memory_pressure.avg10),
582                 LOADAVG_INT_SIDE(ctx->memory_pressure.avg60), LOADAVG_DECIMAL_SIDE(ctx->memory_pressure.avg60),
583                 LOADAVG_INT_SIDE(ctx->memory_pressure.avg300), LOADAVG_DECIMAL_SIDE(ctx->memory_pressure.avg300),
584                 FORMAT_TIMESPAN(ctx->memory_pressure.total, USEC_PER_SEC),
585                 strempty(prefix), FORMAT_BYTES(ctx->current_memory_usage));
586 
587         if (!empty_or_root(ctx->path))
588                 fprintf(f,
589                         "%s\tMemory Min: %s\n"
590                         "%s\tMemory Low: %s\n"
591                         "%s\tPgscan: %" PRIu64 "\n"
592                         "%s\tLast Pgscan: %" PRIu64 "\n",
593                         strempty(prefix), FORMAT_BYTES_CGROUP_PROTECTION(ctx->memory_min),
594                         strempty(prefix), FORMAT_BYTES_CGROUP_PROTECTION(ctx->memory_low),
595                         strempty(prefix), ctx->pgscan,
596                         strempty(prefix), ctx->last_pgscan);
597 }
598 
oomd_dump_system_context(const OomdSystemContext * ctx,FILE * f,const char * prefix)599 void oomd_dump_system_context(const OomdSystemContext *ctx, FILE *f, const char *prefix) {
600         assert(ctx);
601         assert(f);
602 
603         fprintf(f,
604                 "%sMemory: Used: %s Total: %s\n"
605                 "%sSwap: Used: %s Total: %s\n",
606                 strempty(prefix),
607                 FORMAT_BYTES(ctx->mem_used),
608                 FORMAT_BYTES(ctx->mem_total),
609                 strempty(prefix),
610                 FORMAT_BYTES(ctx->swap_used),
611                 FORMAT_BYTES(ctx->swap_total));
612 }
613