pax_global_header00006660000000000000000000000064137416375410014525gustar00rootroot0000000000000052 comment=445bda11fc98f233a4fe87a033e2350f4d93f610 earlyoom-1.6.2/000077500000000000000000000000001374163754100133625ustar00rootroot00000000000000earlyoom-1.6.2/.gitignore000066400000000000000000000002071374163754100153510ustar00rootroot00000000000000*~ /earlyoom # generated from MANPAGE.md /earlyoom.1 /earlyoom.1.gz # generated service files /earlyoom.service /earlyoom.initscript earlyoom-1.6.2/.travis.yml000066400000000000000000000007171374163754100155000ustar00rootroot00000000000000# earlyoom is written in C, but the test suite is written in Go. # The "go" build environment in Travis CI includes gcc, so just # pretend we are a Go project. language: go # Travis by default only pulls the last 50 commits, which means # "git describe" will fail once we are more than 50 commits # away from the last tag. git: depth: 100 addons: apt: packages: - pandoc script: - cc --version - make - make test - make format - git diff earlyoom-1.6.2/CODE_OF_CONDUCT.md000066400000000000000000000064261374163754100161710ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jakobunt@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq earlyoom-1.6.2/Dockerfile000066400000000000000000000002351374163754100153540ustar00rootroot00000000000000FROM gcc as build WORKDIR /usr/src COPY . . ENV CFLAGS -static RUN make ### FROM scratch COPY --from=build /usr/src/earlyoom / ENTRYPOINT ["/earlyoom"] earlyoom-1.6.2/LICENSE000066400000000000000000000020771374163754100143750ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Jakob Unterwurzacher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. earlyoom-1.6.2/MANPAGE-render.sh000077500000000000000000000000561374163754100162470ustar00rootroot00000000000000#!/bin/bash make earlyoom.1 man ./earlyoom.1 earlyoom-1.6.2/MANPAGE.md000066400000000000000000000135361374163754100147640ustar00rootroot00000000000000% earlyoom(1) | General Commands Manual # NAME earlyoom - Early OOM Daemon # SYNOPSIS **earlyoom** [**OPTION**]... # DESCRIPTION The oom-killer generally has a bad reputation among Linux users. One may have to sit in front of an unresponsive system, listening to the grinding disk for minutes, and press the reset button to quickly get back to what one was doing after running out of patience. **earlyoom** checks the amount of available memory and free swap up to 10 times a second (less often if there is a lot of free memory). If **both** memory **and** swap are below 10%, it will kill the largest process (highest `oom_score`). The percentage value is configurable via command line arguments. If there is a failure when trying to kill a process, **earlyoom** sleeps for 1 second to limit log spam due to recurring errors. # OPTIONS #### -m PERCENT[,KILL_PERCENT] set available memory minimum to PERCENT of total (default 10 %). earlyoom starts sending SIGTERM once **both** memory **and** swap are below their respective PERCENT setting. It sends SIGKILL once **both** are below their respective KILL_PERCENT setting (default PERCENT/2). Use the same value for PERCENT and KILL_PERCENT if you always want to use SIGKILL. Examples: earlyoom # sets PERCENT=10, KILL_PERCENT=5 earlyoom -m 30 # sets PERCENT=30, KILL_PERCENT=15 earlyoom -m 20,18 # sets PERCENT=20, KILL_PERCENT=18 #### -s PERCENT[,KILL_PERCENT] set free swap minimum to PERCENT of total (default 10 %). Send SIGKILL if at or below KILL_PERCENT (default PERCENT/2), otherwise SIGTERM. You can use `-s 100` to have earlyoom effectively ignore swap usage: Processes are killed once available memory drops below the configured minimum, no matter how much swap is free. Use the same value for PERCENT and KILL_PERCENT if you always want to use SIGKILL. #### -M SIZE[,KILL_SIZE] As an alternative to specifying a percentage of total memory, `-M` sets the available memory minimum to SIZE KiB. The value is internally converted to a percentage. If you pass both `-M` and `-m`, the lower value is used. Example: Reserve 10% of RAM but at most 1 GiB: earlyoom -m 10 -M 1048576 earlyoom sends SIGKILL if at or below KILL_SIZE (default SIZE/2), otherwise SIGTERM. #### -S SIZE[,KILL_SIZE] As an alternative to specifying a percentage of total swap, `-S` sets the free swap minimum to SIZE KiB. The value is internally converted to a percentage. If you pass both `-S` and `-s`, the lower value is used. Send SIGKILL if at or below KILL_SIZE (default SIZE/2), otherwise SIGTERM. #### -k removed in earlyoom v1.2, ignored for compatibility #### -i user-space oom killer should ignore positive oom_score_adj values #### -d enable debugging messages #### -v print version information and exit #### -r INTERVAL Time between printing periodic memory reports, in seconds (default 1.0). A memory report looks like this: mem avail: 21790 of 23909 MiB (91.14%), swap free: 0 of 0 MiB ( 0.00%) Set to 3600 to print a report every hour, to 86400 to print once a day etc. Set to 0 to disable printing periodic memory reports. Free memory monitoring and low-memory killing runs independently of this option at an adaptive poll rate that only depends on free memory. Due to the adaptive poll rate, when there is a lot of free memory, the actual interval may be up to 1 second longer than the setting. #### -p Increase earlyoom's priority: set niceness of earlyoom to -20 and oom_score_adj to -100. When earlyoom is run through its default systemd service, the `-p` switch doesn't work. To achieve the same effect, enter the following three lines into `sudo systemctl edit earlyoom`: [Service] OOMScoreAdjust=-100 Nice=-20 #### -n Enable notifications via d-bus. #### \-\-prefer REGEX prefer killing processes matching REGEX (adds 300 to oom_score) #### \-\-avoid REGEX avoid killing processes matching REGEX (subtracts 300 from oom_score) #### \-\-dryrun dry run (do not kill any processes) #### -h, \-\-help this help text # EXIT STATUS 0: Successful program execution. 1: Other error - inspect message for details 2: Switch conflict. 4: Could not cd to /proc 5: Could not open proc 7: Could not open /proc/sysrq-trigger 13: Unknown options. 14: Wrong parameters for other options. 15: Wrong parameters for memory threshold. 16: Wrong parameters for swap threshold. 102: Could not open /proc/meminfo 103: Could not read /proc/meminfo 104: Could not find a specific entry in /proc/meminfo 105: Could not convert number when parse the contents of /proc/meminfo # Why not trigger the kernel oom killer? Earlyoom does not use `echo f > /proc/sysrq-trigger` because the Chrome people made their browser always be the first (innocent!) victim by setting `oom_score_adj` very high. Instead, earlyoom finds out itself by reading through `/proc/*/status` (actually `/proc/*/statm`, which contains the same information but is easier to parse programmatically). Additionally, in recent kernels (tested on 4.0.5), triggering the kernel oom killer manually may not work at all. That is, it may only free some graphics memory (that will be allocated immediately again) and not actually kill any process. # MEMORY USAGE About 2 MiB VmRSS. All memory is locked using mlockall() to make sure earlyoom does not slow down in low memory situations. # BUGS If there is zero total swap on earlyoom startup, any `-S` (uppercase "S") values are ignored, a warning is printed, and default swap percentages are used. For processes matched by `--prefer`, negative `oom_score_adj` values are not taken into account, and the process gets an effective `oom_score` of at least 300. See https://github.com/rfjakob/earlyoom/issues/159 for details. # AUTHOR The author of earlyoom is Jakob Unterwurzacher ⟨jakobunt@gmail.com⟩. This manual page was written by Yangfl ⟨mmyangfl@gmail.com⟩, for the Debian project (and may be used by others). earlyoom-1.6.2/Makefile000066400000000000000000000047411374163754100150300ustar00rootroot00000000000000VERSION ?= $(shell git describe --tags --dirty 2> /dev/null) CFLAGS += -Wall -Wextra -Wformat-security -Wconversion -DVERSION=\"$(VERSION)\" -g -fstack-protector-all -std=gnu99 DESTDIR ?= PREFIX ?= /usr/local BINDIR ?= /bin SYSCONFDIR ?= /etc SYSTEMDUNITDIR ?= $(SYSCONFDIR)/systemd/system PANDOC := $(shell command -v pandoc 2> /dev/null) ifeq ($(VERSION),) VERSION := "(unknown version)" endif .PHONY: all clean install uninstall format test all: earlyoom earlyoom.1 earlyoom.service earlyoom: $(wildcard *.c *.h) Makefile $(CC) $(LDFLAGS) $(CPPFLAGS) $(CFLAGS) -o $@ $(wildcard *.c) earlyoom.1: MANPAGE.md ifdef PANDOC pandoc MANPAGE.md -s -t man > earlyoom.1 else @echo "pandoc is not installed, skipping earlyoom.1 manpage generation" endif clean: rm -f earlyoom earlyoom.service earlyoom.initscript earlyoom.1 earlyoom.1.gz install: earlyoom.service install-bin install-default install-man install -d $(DESTDIR)$(SYSTEMDUNITDIR) install -m 644 $< $(DESTDIR)$(SYSTEMDUNITDIR) -chcon -t systemd_unit_file_t $(DESTDIR)$(SYSTEMDUNITDIR)/$< -systemctl enable earlyoom install-initscript: earlyoom.initscript install-bin install-default install -d $(DESTDIR)$(SYSCONFDIR)/init.d/ install -m 755 $< $(DESTDIR)$(SYSCONFDIR)/init.d/earlyoom -update-rc.d earlyoom start 18 2 3 4 5 . stop 20 0 1 6 . earlyoom.%: earlyoom.%.in sed "s|:TARGET:|$(PREFIX)$(BINDIR)|g;s|:SYSCONFDIR:|$(SYSCONFDIR)|g" $< > $@ install-default: earlyoom.default install-man install -d $(DESTDIR)$(SYSCONFDIR)/default/ install -m 644 $< $(DESTDIR)$(SYSCONFDIR)/default/earlyoom install-bin: earlyoom install -d $(DESTDIR)$(PREFIX)$(BINDIR)/ install -m 755 $< $(DESTDIR)$(PREFIX)$(BINDIR)/ install-man: earlyoom.1.gz ifdef PANDOC install -d $(DESTDIR)$(PREFIX)/share/man/man1/ install -m 644 $< $(DESTDIR)$(PREFIX)/share/man/man1/ endif earlyoom.1.gz: earlyoom.1 ifdef PANDOC gzip -f -k -n $< endif uninstall: uninstall-bin uninstall-man systemctl disable earlyoom rm -f $(DESTDIR)$(SYSTEMDUNITDIR)/earlyoom.service uninstall-man: rm -f $(DESTDIR)$(PREFIX)/share/man/man1/earlyoom.1.gz uninstall-initscript: uninstall-bin rm -f $(DESTDIR)$(SYSCONFDIR)/init.d/earlyoom update-rc.d earlyoom remove uninstall-bin: rm -f $(DESTDIR)$(PREFIX)$(BINDIR)/earlyoom # Depends on earlyoom compilation to make sure the syntax is ok. format: earlyoom clang-format --style=WebKit -i *.h *.c go fmt . test: earlyoom cppcheck -q . || echo "skipping optional cppcheck" go test -v .PHONY: bench bench: go test -run=NONE -bench=. earlyoom-1.6.2/README.md000066400000000000000000000401471374163754100146470ustar00rootroot00000000000000earlyoom - The Early OOM Daemon =============================== [![Build Status](https://api.travis-ci.org/rfjakob/earlyoom.svg)](https://travis-ci.org/rfjakob/earlyoom) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Latest release](https://img.shields.io/github/release/rfjakob/earlyoom.svg)](https://github.com/rfjakob/earlyoom/releases) [![Total alerts](https://img.shields.io/lgtm/alerts/g/rfjakob/earlyoom.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/rfjakob/earlyoom/alerts/) [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/rfjakob/earlyoom.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/rfjakob/earlyoom/context:cpp) The oom-killer generally has a bad reputation among Linux users. This may be part of the reason Linux invokes it only when it has absolutely no other choice. It will swap out the desktop environment, drop the whole page cache and empty every buffer before it will ultimately kill a process. At least that's what I think that it will do. I have yet to be patient enough to wait for it, sitting in front of an unresponsive system. This made me and other people wonder if the oom-killer could be configured to step in earlier: [reddit r/linux][5], [superuser.com][2], [unix.stackexchange.com][3]. As it turns out, no, it can't. At least using the in-kernel oom-killer. In the user space, however, we can do whatever we want. What does it do --------------- earlyoom checks the amount of available memory and free swap up to 10 times a second (less often if there is a lot of free memory). By default if both are below 10%, it will kill the largest process (highest `oom_score`). The percentage value is configurable via command line arguments. In the `free -m` output below, the available memory is 2170 MiB and the free swap is 231 MiB. total used free shared buff/cache available Mem: 7842 4523 137 841 3182 2170 Swap: 1023 792 231 Why is "available" memory checked as opposed to "free" memory? On a healthy Linux system, "free" memory is supposed to be close to zero, because Linux uses all available physical memory to cache disk access. These caches can be dropped any time the memory is needed for something else. The "available" memory accounts for that. It sums up all memory that is unused or can be freed immediately. Note that you need a recent version of `free` and Linux kernel 3.14+ to see the "available" column. If you have a recent kernel, but an old version of `free`, you can get the value from `grep MemAvailable /proc/meminfo`. When both your available memory and free swap drop below 10% of the total, it will send the `SIGTERM` signal to the process that uses the most memory in the opinion of the kernel (`/proc/*/oom_score`). It can optionally (`-i` option) ignore any positive adjustments set in `/proc/*/oom_score_adj` to protect innocent victims (see below). #### See also * [nohang](https://github.com/hakavlad/nohang), a similar project like earlyoom, written in Python and with additional features and configuration options. * facebooks's pressure stall information (psi) [kernel patches](http://git.cmpxchg.org/cgit.cgi/linux-psi.git/) and the accompanying [oomd](https://github.com/facebookincubator/oomd) userspace helper. The patches are merged in Linux 4.20. Why not trigger the kernel oom killer? -------------------------------------- Earlyoom does not use `echo f > /proc/sysrq-trigger` because [the Chrome people made their browser (and all electron-based apps - vscode, skype, discord etc) always be the first (innocent!) victim by setting `oom_score_adj` very high]( https://code.google.com/p/chromium/issues/detail?id=333617). Instead, earlyoom finds out itself by reading through `/proc/*/status` (actually `/proc/*/statm`, which contains the same information but is easier to parse programmatically). Additionally, in recent kernels (tested on 4.0.5), triggering the kernel oom killer manually may not work at all. That is, it may only free some graphics memory (that will be allocated immediately again) and not actually kill any process. [Here](https://gist.github.com/rfjakob/346b7dc611fc3cdf4011) you can see how this looks like on my machine (Intel integrated graphics). How much memory does earlyoom use? ---------------------------------- About `2 MiB` (`VmRSS`), though only `220 kiB` is private memory (`RssAnon`). The rest is the libc library (`RssFile`) that is shared with other processes. All memory is locked using `mlockall()` to make sure earlyoom does not slow down in low memory situations. Download and compile -------------------- Compiling yourself is easy: ```bash git clone https://github.com/rfjakob/earlyoom.git cd earlyoom make ``` Optional: Run the integrated self-tests: ```bash make test ``` Start earlyoom automatically by registering it as a service: ```bash sudo make install # systemd sudo make install-initscript # non-systemd ``` _Note that for systems with SELinux disabled (Ubuntu 19.04, Debian 9 ...) chcon warnings reporting failure to set the context can be safely ignored._ For Debian 10+ and Ubuntu 18.04+, there's a [Debian package](https://packages.debian.org/search?keywords=earlyoom): ```bash sudo apt install earlyoom ``` For Fedora and RHEL 8 with EPEL, there's a [Fedora package](https://apps.fedoraproject.org/packages/earlyoom): ```bash sudo dnf install earlyoom sudo systemctl enable --now earlyoom ``` For Arch Linux, there's an [Arch Linux package](https://www.archlinux.org/packages/community/x86_64/earlyoom/): ```bash sudo pacman -S earlyoom sudo systemctl enable --now earlyoom ``` Availability in other distributions: see [repology page](https://repology.org/project/earlyoom/versions). Use --- Just start the executable you have just compiled: ```bash ./earlyoom ``` It will inform you how much memory and swap you have, what the minimum is, how much memory is available and how much swap is free. ``` ./earlyoom earlyoom v1.4-6-ga4021ae mem total: 9823 MiB, swap total: 9823 MiB sending SIGTERM when mem <= 10 % and swap <= 10 %, SIGKILL when mem <= 5 % and swap <= 5 % Could not lock memory - continuing anyway: Cannot allocate memory mem avail: 5091 of 9823 MiB (51 %), swap free: 9823 of 9823 MiB (100 %) mem avail: 5084 of 9823 MiB (51 %), swap free: 9823 of 9823 MiB (100 %) mem avail: 5086 of 9823 MiB (51 %), swap free: 9823 of 9823 MiB (100 %) [...] ``` If the values drop below the minimum, processes are killed until it is above the minimum again. Every action is logged to stderr. If you are running earlyoom as a systemd service, you can view the last 10 lines using ```bash systemctl status earlyoom ``` ### Notifications Since version 1.6, earlyoom can send notifications about killed processes via the system d-bus. Pass `-n` to enable them. To actually see the notifications in your GUI session, you need to have [systembus-notify](https://github.com/rfjakob/systembus-notify) running as your user. ### Preferred Processes The command-line flag `--prefer` specifies processes to prefer killing; likewise, `--avoid` specifies processes to avoid killing. The list of processes is specified by a regex expression. For instance, to avoid having `foo` and `bar` be killed: ```bash earlyoom --avoid '^(foo|bar)$' ``` The regex is matched against the basename of the process as shown in `/proc/PID/stat`. Configuration file ------------------ If you are running earlyoom as a system service (through systemd or init.d), you can adjust its configuration via the file provided in `/etc/default/earlyoom`. The file already contains some examples in the comments, which you can use to build your own set of configuration based on the supported command line options, for example: ``` EARLYOOM_ARGS="-m 5 -r 60 --avoid '(^|/)(init|Xorg|ssh)$' --prefer '(^|/)(java|chromium)$'" ``` After adjusting the file, simply restart the service to apply the changes. For example, for systemd: ```bash systemctl restart earlyoom ``` Please note that this configuration file has no effect on earlyoom instances outside of systemd/init.d. Command line options -------------------- ``` ./earlyoom -h earlyoom v1.6-preview Usage: ./earlyoom [OPTION]... -m PERCENT[,KILL_PERCENT] set available memory minimum to PERCENT of total (default 10 %). earlyoom sends SIGTERM once below PERCENT, then SIGKILL once below KILL_PERCENT (default PERCENT/2). -s PERCENT[,KILL_PERCENT] set free swap minimum to PERCENT of total (default 10 %). Note: both memory and swap must be below minimum for earlyoom to act. -M SIZE[,KILL_SIZE] set available memory minimum to SIZE KiB -S SIZE[,KILL_SIZE] set free swap minimum to SIZE KiB -i user-space oom killer should ignore positive oom_score_adj values -n enable d-bus notifications -d enable debugging messages -v print version information and exit -r INTERVAL memory report interval in seconds (default 1), set to 0 to disable completely -p set niceness of earlyoom to -20 and oom_score_adj to -100 --prefer REGEX prefer to kill processes matching REGEX --avoid REGEX avoid killing processes matching REGEX --dryrun dry run (do not kill any processes) -h, --help this help text ``` See the [man page](MANPAGE.md) for details. Contribute ---------- Bug reports and pull requests are welcome via github. In particular, I am glad to accept * Use case reports and feedback Changelog --------- * v1.6.2, 2020-10-14 * Double-check memory situation before killing victim ([commit](https://github.com/rfjakob/earlyoom/commit/e34e0fcec5d9f60eb19a48a3ec2bab175818fdd8)) * Never terminate ourselves ([#205](https://github.com/rfjakob/earlyoom/issues/205)) * Dump buffer on /proc/meminfo conversion error ([#214](https://github.com/rfjakob/earlyoom/issues/214)) * 1.6.1, 2020-07-07 * Clean up dbus-send zombie processes ([#200](https://github.com/rfjakob/earlyoom/issues/200)) * Skip processes with oom_score_adj=-1000 ([210](https://github.com/rfjakob/earlyoom/issues/210)) * 1.6, 2020-04-11 * Replace old `notify-send` GUI notification logic with `dbus-send` / [systembus-notify](https://github.com/rfjakob/systembus-notify) ([#183](https://github.com/rfjakob/earlyoom/issues/183)) * `-n`/`-N` now enables the new logic * You need to have [systembus-notify](https://github.com/rfjakob/systembus-notify) running in your GUI session for notifications for work * Handle `/proc` mounted with [hidepid](https://github.com/rfjakob/earlyoom/wiki/proc-hidepid) gracefully ([issue #184](https://github.com/rfjakob/earlyoom/issues/184)) * v1.5, 2020-03-22 * `-p`: set oom_score_adj to `-100` instead of `-1000` ([#170](https://github.com/rfjakob/earlyoom/issues/170)) * Allow using **both** `-M` and `-m`, and `-S` and `-s`. The lower value (converted to percentages) will be used. * Set memory report interval in `earlyoom.default` to 1 hour instead of 1 minute ([#177](https://github.com/rfjakob/earlyoom/issues/177)) * v1.4, 2020-03-01 * Make victim selection logic 50% faster by lazy-loading process attributes * Log the user id `uid` of killed processes in addition to pid and name * Color debug log in light grey * Code clean-up * Use block-local variables where possible * Introduce PATH_LEN to replace several hardcoded buffer lengths * Expand testsuite (`make test`) * Run `cppcheck` when available * Add unit-test benchmarks (`make bench`) * Drop root privileges in systemd unit file `earlyoom.service` * v1.3.1, 2020-02-27 * Fix spurious testsuite failure on systems with a lot of RAM ([issue #156](https://github.com/rfjakob/earlyoom/issues/156)) * v1.3, 2019-05-26 * Wait for processes to actually exit when sending a signal * This fixes the problem that earlyoom sometimes kills more than one process when one would be enough ([issue #121](https://github.com/rfjakob/earlyoom/issues/121)) * Be more liberal in what limits to accepts for SIGTERM and SIGKILL ([issue #97](https://github.com/rfjakob/earlyoom/issues/97)) * Don't exit with a fatal error if SIGTERM limit < SIGKILL limit * Allow zero SIGKILL limit * Reformat startup output to make it clear that BOTH swap and mem must be <= limit * Add [notify_all_users.py](contrib/notify_all_users.py) helper script * Add [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) (Contributor Covenant 1.4) ([#102](https://github.com/rfjakob/earlyoom/issues/102)) * Fix possibly truncated UTF8 app names in log output ([#110](https://github.com/rfjakob/earlyoom/issues/110)) * v1.2, 2018-10-28 * Implement adaptive sleep time (= adaptive poll rate) to lower CPU usage further ([issue #61](https://github.com/rfjakob/earlyoom/issues/61)) * Remove option to use kernel oom-killer (`-k`, now ignored for compatibility) ([issue #80](https://github.com/rfjakob/earlyoom/issues/80)) * Gracefully handle the case of swap being added or removed after earlyoom was started ([issue 62](https://github.com/rfjakob/earlyoom/issues/62), [commit](https://github.com/rfjakob/earlyoom/commit/88e58903fec70b105aebba39cd584add5e1d1532)) * Implement staged kill: first SIGTERM, then SIGKILL, with configurable limits ([issue #67](https://github.com/rfjakob/earlyoom/issues/67)) * v1.1, 2018-07-07 * Fix possible shell code injection through GUI notifications ([commit](https://github.com/rfjakob/earlyoom/commit/ab79aa3895077676f50120f15e2bb22915446db9)) * On failure to kill any process, only sleep 1 second instead of 10 ([issue #74](https://github.com/rfjakob/earlyoom/issues/74)) * Send the GUI notification *after* killing, not before ([issue #73](https://github.com/rfjakob/earlyoom/issues/73)) * Accept `--help` in addition to `-h` * Fix wrong process name in log and in kill notification ([commit 1](https://github.com/rfjakob/earlyoom/commit/7634c5b66dd7e9b88c6ebf0496c8777f3c4b3cc1), [commit 2](https://github.com/rfjakob/earlyoom/commit/15679a3b768ea2df9b13a7d9b0c1e30bd1a450e6), [issue #52](https://github.com/rfjakob/earlyoom/issues/52), [issue #65](https://github.com/rfjakob/earlyoom/issues/65), [issue #194](https://github.com/rfjakob/earlyoom/issues/194)) * Fix possible division by zero with `-S` ([commit](https://github.com/rfjakob/earlyoom/commit/a0c4b26dfef8b38ef81c7b0b907442f344a3e115)) * v1.0, 2018-01-28 * Add `--prefer` and `--avoid` options (@TomJohnZ) * Add support for GUI notifications, add options `-n` and `-N` * v0.12: Add `-M` and `-S` options (@nailgun); add man page, parameterize Makefile (@yangfl) * v0.11: Fix undefined behavoir in get_entry_fatal (missing return, [commit](https://github.com/rfjakob/earlyoom/commit/9251d25618946723eb8a829404ebf1a65d99dbb0)) * v0.10: Allow to override Makefile's VERSION variable to make packaging easier, add `-v` command-line option * v0.9: If oom_score of all processes is 0, use VmRss to find a victim * v0.8: Use a guesstimate if the kernel does not provide MemAvailable * v0.7: Select victim by oom_score instead of VmRSS, add options `-i` and `-d` * v0.6: Add command-line options `-m`, `-s`, `-k` * v0.5: Add swap support * v0.4: Add SysV init script (thanks [@joeytwiddle](https://github.com/joeytwiddle)), use the new `MemAvailable` from `/proc/meminfo` (needs Linux 3.14+, [commit][4]) * v0.2: Add systemd unit file * v0.1: Initial release [1]: http://www.freelists.org/post/procps/library-properly-handle-memory-used-by-tmpfs [2]: http://superuser.com/questions/406101/is-it-possible-to-make-the-oom-killer-intervent-earlier [3]: http://unix.stackexchange.com/questions/38507/is-it-possible-to-trigger-oom-killer-on-forced-swapping [4]: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 [5]: https://www.reddit.com/r/linux/comments/56r4xj/why_are_low_memory_conditions_handled_so_badly/ earlyoom-1.6.2/contrib/000077500000000000000000000000001374163754100150225ustar00rootroot00000000000000earlyoom-1.6.2/contrib/.gitignore000066400000000000000000000000071374163754100170070ustar00rootroot00000000000000zombie earlyoom-1.6.2/contrib/Makefile000066400000000000000000000001551374163754100164630ustar00rootroot00000000000000.PHONY: format format: autopep8 -i *.py clang-format -i *.c zombie: gcc -Wall -Wextra -o zombie zombie.c earlyoom-1.6.2/contrib/fillmem.py000077500000000000000000000007071374163754100170300ustar00rootroot00000000000000#!/usr/bin/env python3 # # fillmem.py allocates a configurable amount of memory # and sleeps forever. # # Originally posted by swiftcoder at # https://unix.stackexchange.com/a/99365/45823 import sys import time import os if len(sys.argv) != 2: print("usage: ./fillmem.py MEGABYTES") sys.exit() count = int(sys.argv[1]) data = bytearray(1024*1024*count) while True: os.system("grep VmRSS /proc/%d/status" % (os.getpid())) time.sleep(1) earlyoom-1.6.2/contrib/membomb/000077500000000000000000000000001374163754100164405ustar00rootroot00000000000000earlyoom-1.6.2/contrib/membomb/.gitignore000066400000000000000000000000111374163754100204200ustar00rootroot00000000000000/membomb earlyoom-1.6.2/contrib/membomb/Makefile000066400000000000000000000002121374163754100200730ustar00rootroot00000000000000CFLAGS += -Wall -Wextra -g -fstack-protector-all -std=gnu99 membomb: $(wildcard *.c *.h) Makefile $(CC) $(CFLAGS) -o $@ $(wildcard *.c) earlyoom-1.6.2/contrib/membomb/membomb.c000066400000000000000000000031301374163754100202170ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* Use up all memory that we can get, as fast as we can. * As progress information, prints how much memory we already * have. * * This file is part of the earlyoom project: https://github.com/rfjakob/earlyoom */ #include #include #include #include #include #include #define NUM_PAGES 10 void handle_sigterm(int sig) { printf("blocking SIGTERM %d\n", sig); } int main() { long page_size = sysconf(_SC_PAGESIZE); long bs = page_size * NUM_PAGES; long cnt = 0, last_sum = 0; struct timeval tv1; char* p; signal(SIGTERM, handle_sigterm); gettimeofday(&tv1, NULL); while (1) { p = malloc(bs); if (!p) { printf("malloc failed\n"); continue; } for (int i = 0; i < NUM_PAGES; i++) { // Write to each page so the kernel really has to allocate it. p[i * page_size] = 0xab; } cnt++; if (cnt % 1000 == 0) { long sum = bs * cnt / 1024 / 1024; struct timeval tv2; gettimeofday(&tv2, NULL); long delta = tv2.tv_sec - tv1.tv_sec; // Convert to microseconds delta *= 1000000; // Add microsecond delta delta = delta + tv2.tv_usec - tv1.tv_usec; // Micro-MB-per-Microsecond = MB/s long mbps = (sum - last_sum) * 1000000 / delta; printf("%4ld MiB (%4ld MiB/s)\n", sum, mbps); last_sum = sum; gettimeofday(&tv1, NULL); } } } earlyoom-1.6.2/contrib/memforkbomb.sh000077500000000000000000000010211374163754100176530ustar00rootroot00000000000000#!/bin/bash # # Combined memory- and fork-bomb. You may # want to save all your open files before # running this. # # Forks a new memory hog every two seconds, # setting oom_score_adj to 1000 to avoid # causing damage to other processes. # # Each memory hog eats up memory as fast as # it can (I just use `tail /dev/zero`). # # On my machine, earlyoom keeps up killing # the hogs fast enough to not cause any # slowdown. set -ux while sleep 2 ; do tail /dev/zero & PID=$! echo 1000 > /proc/$PID/oom_score_adj || exit 1 done earlyoom-1.6.2/contrib/mon.sh000077500000000000000000000035211374163754100161530ustar00rootroot00000000000000#!/bin/bash # # Monitor the memory usage and state of a PID in # a 0.1 second loop. # # Example with "sudo memtester 10G" running in the background: # # $ ./mon.sh $(pgrep memtester) # 0 MemAvailable: 10158052 kB VmRSS: 10487200 kB statm: 2622018 2621800 346 4 0 2621518 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 10153776 kB VmRSS: 10487200 kB statm: 2622018 2621800 346 4 0 2621518 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 10153532 kB VmRSS: 10487200 kB statm: 2622018 2621800 346 4 0 2621518 0 stat: 9067 (memtester) R 9065 9065 4 # ***sudo pkill memtester*** # 0 MemAvailable: 10154260 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 10154296 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 10154280 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 10154256 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 10146932 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 11038764 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 13773280 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 16593848 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # 0 MemAvailable: 19330180 kB statm: 0 0 0 0 0 0 0 stat: 9067 (memtester) R 9065 9065 4 # ***actual exit*** # 1 MemAvailable: 20632628 kB statm: stat: # 1 MemAvailable: 20632992 kB statm: stat: # 1 MemAvailable: 20633244 kB statm: stat: set -u while sleep 0.1; do test -e /proc/$1 PROC=$? AVAIL=$(grep MemAvailable /proc/meminfo) RSS=$(grep VmRSS /proc/$1/status 2> /dev/null) STATM=$(cat /proc/$1/statm 2> /dev/null) STAT=$(head -c 30 /proc/$1/stat 2> /dev/null) echo "$PROC $AVAIL $RSS statm: $STATM stat: $STAT" done earlyoom-1.6.2/contrib/oomstat/000077500000000000000000000000001374163754100165105ustar00rootroot00000000000000earlyoom-1.6.2/contrib/oomstat/.gitignore000066400000000000000000000000101374163754100204670ustar00rootroot00000000000000oomstat earlyoom-1.6.2/contrib/oomstat/Makefile000066400000000000000000000000431374163754100201450ustar00rootroot00000000000000.PHONY: oomstat oomstat: go build earlyoom-1.6.2/contrib/oomstat/README.md000066400000000000000000000142041374163754100177700ustar00rootroot00000000000000# oomstat oomstat is a very simple tool that prints available memory, free swap and memory pressure (PSI) information every 100ms. Example output with `tail /dev/zero` started in the background is pasted below, more examples are available in the text files in this directory. ``` $ ./oomstat ./oomstat [sudo] password for jakob: | /proc/meminfo | /proc/pressure/memory Time | MemAvail SwapFree | some avg10 full avg10 s | MiB MiB | % % % % - - [...] 1.5 | 20251 0 | 0 6 0 3 1.6 | 20148 0 | 0 6 0 3 1.7 | 19927 0 | 0 6 0 3 1.8 | 19700 0 | 0 6 0 3 1.9 | 19474 0 | 0 6 0 3 2.0 | 19251 0 | 0 6 0 3 2.1 | 19019 0 | 0 6 0 3 2.2 | 18794 0 | 0 6 0 3 2.3 | 18564 0 | 0 6 0 3 2.4 | 18333 0 | 0 6 0 3 2.5 | 18100 0 | 0 6 0 3 2.6 | 17871 0 | 0 6 0 3 2.7 | 17638 0 | 0 6 0 3 2.8 | 17407 0 | 0 6 0 3 2.9 | 17172 0 | 0 6 0 3 3.0 | 16945 0 | 0 6 0 3 3.1 | 16715 0 | 0 6 0 3 3.2 | 16482 0 | 0 6 0 3 3.3 | 16249 0 | 0 6 0 3 3.4 | 16016 0 | 0 6 0 3 3.5 | 15783 0 | 0 5 0 3 3.6 | 15556 0 | 0 5 0 3 3.7 | 15318 0 | 0 5 0 3 3.8 | 15086 0 | 0 5 0 3 3.9 | 14852 0 | 0 5 0 3 4.0 | 14619 0 | 0 5 0 3 4.1 | 14382 0 | 0 5 0 3 4.2 | 14148 0 | 0 5 0 3 4.3 | 13913 0 | 0 5 0 3 4.4 | 13679 0 | 0 5 0 3 4.5 | 13444 0 | 0 5 0 3 4.6 | 13212 0 | 0 5 0 3 4.7 | 12974 0 | 0 5 0 3 4.8 | 12743 0 | 0 5 0 3 4.9 | 12503 0 | 0 5 0 3 5.0 | 12267 0 | 0 5 0 3 5.1 | 12038 0 | 0 5 0 3 5.2 | 11800 0 | 0 5 0 3 5.3 | 11564 0 | 0 5 0 3 5.4 | 11337 0 | 0 5 0 3 5.5 | 11102 0 | 0 4 0 2 5.6 | 10868 0 | 0 4 0 2 5.7 | 10631 0 | 0 4 0 2 5.8 | 10397 0 | 0 4 0 2 5.9 | 10160 0 | 0 4 0 2 6.0 | 9925 0 | 0 4 0 2 6.1 | 9690 0 | 0 4 0 2 6.2 | 9457 0 | 0 4 0 2 6.3 | 9223 0 | 0 4 0 2 6.4 | 8991 0 | 0 4 0 2 6.5 | 8751 0 | 0 4 0 2 6.6 | 8515 0 | 0 4 0 2 6.7 | 8281 0 | 0 4 0 2 6.8 | 8045 0 | 0 4 0 2 6.9 | 7806 0 | 0 4 0 2 7.0 | 7572 0 | 0 4 0 2 7.1 | 7336 0 | 0 4 0 2 7.2 | 7105 0 | 0 4 0 2 7.3 | 6866 0 | 0 4 0 2 7.4 | 6631 0 | 0 4 0 2 7.5 | 6396 0 | 0 3 0 2 7.6 | 6160 0 | 0 3 0 2 7.7 | 5921 0 | 0 3 0 2 7.8 | 5689 0 | 0 3 0 2 7.9 | 5452 0 | 0 3 0 2 8.0 | 5219 0 | 0 3 0 2 8.1 | 4983 0 | 0 3 0 2 8.2 | 4748 0 | 0 3 0 2 8.3 | 4513 0 | 0 3 0 2 8.4 | 4278 0 | 0 3 0 2 8.5 | 4038 0 | 0 3 0 2 8.6 | 3805 0 | 0 3 0 2 8.7 | 3570 0 | 0 3 0 2 8.8 | 3338 0 | 0 3 0 2 8.9 | 3103 0 | 0 3 0 2 9.0 | 2868 0 | 0 3 0 2 9.1 | 2637 0 | 0 3 0 2 9.2 | 2405 0 | 0 3 0 2 9.3 | 2167 0 | 0 3 0 2 9.4 | 1935 0 | 0 3 0 2 9.5 | 1701 0 | 0 2 0 1 9.6 | 1467 0 | 0 2 0 1 9.7 | 1231 0 | 0 2 0 1 9.8 | 999 0 | 0 2 0 1 9.9 | 767 0 | 0 2 0 1 10.0 | 532 0 | 0 2 0 1 10.1 | 313 0 | 0 2 0 1 10.2 | 91 0 | 5 2 3 1 10.3 | 52 0 | 61 2 43 1 10.4 | 29 0 | 55 2 39 1 10.5 | 0 0 | 64 2 46 1 10.6 | 0 0 | 95 2 75 1 10.7 | 0 0 | 98 2 85 1 11.3 | 0 0 | 99 12 91 9 13.2 | 0 0 | 100 28 91 24 14.3 | 2014 0 | 88 28 47 24 14.4 | 6915 0 | 18 28 10 24 14.5 | 12071 0 | 0 28 0 24 14.6 | 17071 0 | 0 28 0 24 14.7 | 20290 0 | 0 28 0 24 14.8 | 20283 0 | 0 28 0 24 14.9 | 20283 0 | 0 28 0 24 15.0 | 20283 0 | 0 28 0 24 15.1 | 20283 0 | 0 28 0 24 15.2 | 20283 0 | 0 28 0 24 15.3 | 20277 0 | 0 28 0 24 15.4 | 20267 0 | 0 28 0 24 15.5 | 20267 0 | 0 28 0 24 15.6 | 20266 0 | 0 30 0 26 15.7 | 20266 0 | 0 30 0 26 15.8 | 20267 0 | 0 30 0 26 15.9 | 20260 0 | 0 30 0 26 16.0 | 20261 0 | 0 30 0 26 16.1 | 20264 0 | 0 30 0 26 16.2 | 20265 0 | 0 30 0 26 16.3 | 20259 0 | 0 30 0 26 16.4 | 20262 0 | 0 30 0 26 ``` earlyoom-1.6.2/contrib/oomstat/loadshift.txt000066400000000000000000000511451374163754100212340ustar00rootroot00000000000000# Loadshift example # # You have an app that uses 70% of your RAM but is idle, and you # have enough swap free. Then another app wants to use 70% of your # RAM. PSI goes crazy, but the situation calms down as the idle app # is swapped out. The right thing here is not killing anything. # # https://github.com/rfjakob/earlyoom/issues/100#issuecomment-508562342 | /proc/meminfo | /proc/pressure/memory Time | MemAvail SwapFree | some avg10 full avg10 s | MiB MiB | % % % % - - 0.0 | 5512 6669 | 0 0 0 0 0.1 | 5511 6669 | 0 0 0 0 0.2 | 5511 6669 | 0 0 0 0 0.3 | 5511 6669 | 0 0 0 0 0.4 | 5511 6669 | 0 0 0 0 0.5 | 5511 6669 | 0 0 0 0 0.6 | 5511 6669 | 0 0 0 0 0.7 | 5513 6669 | 0 0 0 0 0.8 | 5513 6669 | 0 0 0 0 0.9 | 5513 6669 | 0 0 0 0 1.0 | 5513 6669 | 0 0 0 0 1.1 | 5511 6669 | 0 0 0 0 1.2 | 5511 6669 | 0 0 0 0 1.3 | 5511 6669 | 0 0 0 0 1.4 | 5511 6669 | 0 0 0 0 1.5 | 5511 6669 | 0 0 0 0 1.6 | 5511 6669 | 0 0 0 0 1.7 | 5511 6669 | 0 0 0 0 1.8 | 5511 6669 | 0 0 0 0 1.9 | 5511 6669 | 0 0 0 0 2.0 | 5512 6669 | 0 0 0 0 2.1 | 5512 6669 | 0 0 0 0 2.2 | 5512 6669 | 0 0 0 0 2.3 | 5512 6669 | 0 0 0 0 2.4 | 5509 6669 | 0 0 0 0 2.5 | 5325 6669 | 0 0 0 0 2.6 | 5133 6669 | 30 0 8 0 2.7 | 4943 6669 | 42 0 17 0 2.8 | 4752 6669 | 47 0 18 0 2.9 | 4584 6669 | 39 0 12 0 3.0 | 4421 6669 | 30 0 10 0 3.1 | 4193 6669 | 46 0 18 0 3.2 | 3972 6669 | 40 0 13 0 3.3 | 3785 6669 | 39 0 15 0 3.4 | 3563 6669 | 45 0 14 0 3.5 | 3426 6669 | 60 0 26 0 3.6 | 3232 6669 | 35 3 14 1 3.7 | 3016 6669 | 32 3 15 1 3.8 | 2821 6669 | 38 3 15 1 3.9 | 2611 6669 | 40 3 15 1 4.0 | 2528 6669 | 15 3 6 1 4.1 | 2527 6669 | 0 3 0 1 4.2 | 2527 6669 | 0 3 0 1 4.3 | 2527 6669 | 0 3 0 1 4.4 | 2527 6669 | 0 3 0 1 4.5 | 2527 6669 | 0 3 0 1 4.6 | 2527 6669 | 0 3 0 1 4.7 | 2527 6669 | 0 3 0 1 4.8 | 2527 6669 | 0 3 0 1 4.9 | 2527 6669 | 0 3 0 1 5.0 | 2525 6669 | 0 3 0 1 5.1 | 2517 6669 | 0 3 0 1 5.2 | 2517 6669 | 0 3 0 1 5.3 | 2517 6669 | 0 3 0 1 5.4 | 2517 6669 | 0 3 0 1 5.5 | 2517 6669 | 0 3 0 1 5.6 | 2517 6669 | 0 4 0 1 5.7 | 2518 6669 | 0 4 0 1 5.8 | 2518 6669 | 0 4 0 1 5.9 | 2518 6669 | 0 4 0 1 6.0 | 2518 6669 | 0 4 0 1 6.1 | 2518 6669 | 0 4 0 1 6.2 | 2518 6669 | 0 4 0 1 6.3 | 2518 6669 | 0 4 0 1 6.4 | 2510 6669 | 0 4 0 1 6.5 | 2532 6669 | 2 4 1 1 6.6 | 2532 6669 | 0 4 0 1 6.7 | 2532 6669 | 0 4 0 1 6.8 | 2533 6669 | 0 4 0 1 6.9 | 2533 6669 | 0 4 0 1 7.0 | 2529 6669 | 0 4 0 1 7.1 | 2529 6669 | 0 4 0 1 7.2 | 2529 6669 | 0 4 0 1 7.3 | 2527 6669 | 0 4 0 1 7.4 | 2528 6669 | 0 4 0 1 7.5 | 2528 6669 | 0 4 0 1 7.6 | 2528 6669 | 0 3 0 1 7.7 | 2528 6669 | 0 3 0 1 7.8 | 2528 6669 | 0 3 0 1 7.9 | 2528 6669 | 0 3 0 1 8.0 | 2528 6669 | 0 3 0 1 8.1 | 2528 6669 | 0 3 0 1 8.2 | 2528 6669 | 0 3 0 1 8.3 | 2528 6669 | 0 3 0 1 8.4 | 2528 6669 | 0 3 0 1 8.5 | 2448 6669 | 12 3 5 1 8.6 | 2236 6669 | 38 3 17 1 8.7 | 2011 6669 | 32 3 15 1 8.8 | 1811 6669 | 36 3 15 1 8.9 | 1599 6669 | 50 3 24 1 9.0 | 1442 6669 | 19 3 9 1 9.1 | 1279 6669 | 36 3 15 1 9.2 | 1113 6669 | 37 3 15 1 9.3 | 928 6669 | 33 3 14 1 9.4 | 757 6669 | 34 3 16 1 9.5 | 608 6668 | 46 3 27 1 9.6 | 505 6668 | 56 6 25 2 9.7 | 422 6668 | 68 6 29 2 9.8 | 310 6668 | 55 6 28 2 9.9 | 216 6668 | 58 6 30 2 10.0 | 148 6668 | 74 6 17 2 10.1 | 115 6668 | 73 6 24 2 10.2 | 82 6668 | 77 6 30 2 10.3 | 62 6667 | 88 6 34 2 10.5 | 58 6651 | 113 6 8 2 10.6 | 60 6646 | 67 6 7 2 10.7 | 65 6639 | 91 6 5 2 10.8 | 65 6628 | 90 6 24 2 10.9 | 70 6614 | 107 6 9 2 11.0 | 80 6601 | 101 6 11 2 11.1 | 87 6590 | 83 6 9 2 11.2 | 90 6575 | 100 6 10 2 11.3 | 94 6559 | 96 6 11 2 11.4 | 95 6555 | 86 6 2 2 11.5 | 97 6541 | 97 6 2 2 11.6 | 100 6513 | 113 19 5 4 11.8 | 105 6506 | 135 19 6 4 11.9 | 105 6500 | 92 19 3 4 12.0 | 104 6490 | 71 19 9 4 12.1 | 104 6480 | 65 19 2 4 12.2 | 104 6462 | 92 19 6 4 12.3 | 104 6458 | 97 19 5 4 12.4 | 106 6441 | 93 19 4 4 12.5 | 110 6428 | 92 19 4 4 12.6 | 117 6422 | 76 19 6 4 12.7 | 113 6414 | 104 19 3 4 12.8 | 112 6405 | 99 19 9 4 12.9 | 112 6394 | 55 19 3 4 13.1 | 115 6378 | 89 19 9 4 13.2 | 104 6368 | 116 19 3 4 13.3 | 69 6358 | 90 19 6 4 13.4 | 71 6346 | 88 19 5 4 13.5 | 72 6332 | 106 19 0 4 13.6 | 73 6307 | 162 31 7 4 13.8 | 74 6289 | 119 31 11 4 13.9 | 77 6280 | 98 31 5 4 14.0 | 76 6267 | 105 31 6 4 14.1 | 80 6252 | 67 31 5 4 14.2 | 81 6232 | 61 31 2 4 14.3 | 83 6222 | 67 31 6 4 14.4 | 86 6206 | 71 31 4 4 14.5 | 93 6199 | 96 31 2 4 14.6 | 100 6151 | 100 31 3 4 14.8 | 61 6132 | 76 31 2 4 14.9 | 66 6127 | 108 31 6 4 15.0 | 74 6127 | 95 31 3 4 15.1 | 79 6126 | 100 31 3 4 15.2 | 77 6121 | 101 31 5 4 15.3 | 80 6105 | 123 31 7 4 15.4 | 89 6086 | 100 31 8 4 15.6 | 92 6081 | 184 41 7 4 15.7 | 91 6065 | 100 41 10 4 15.9 | 60 6054 | 125 41 16 4 16.0 | 59 6043 | 66 41 3 4 16.1 | 64 6036 | 98 41 8 4 16.2 | 70 6025 | 86 41 6 4 16.3 | 71 6005 | 148 41 0 4 16.4 | 70 5998 | 80 41 3 4 16.6 | 72 5985 | 75 41 8 4 16.7 | 69 5936 | 123 41 4 4 16.9 | 61 5930 | 168 41 1 4 17.1 | 65 5925 | 218 41 14 4 17.2 | 64 5903 | 120 41 11 4 17.3 | 65 5893 | 92 41 3 4 17.5 | 66 5876 | 105 41 13 4 17.6 | 68 5867 | 76 49 13 4 17.8 | 52 5835 | 220 49 6 4 17.9 | 56 5830 | 107 49 6 4 18.0 | 54 5821 | 117 49 9 4 18.1 | 54 5812 | 86 49 11 4 18.2 | 53 5794 | 99 49 10 4 18.3 | 53 5775 | 96 49 10 4 18.5 | 65 5766 | 145 49 17 4 18.6 | 75 5761 | 99 49 1 4 18.7 | 70 5741 | 100 49 5 4 18.8 | 69 5722 | 75 49 3 4 18.9 | 69 5721 | 96 49 1 4 19.0 | 71 5700 | 109 49 4 4 19.1 | 71 5696 | 110 49 6 4 19.2 | 75 5687 | 99 49 4 4 19.3 | 79 5674 | 87 49 2 4 19.4 | 78 5666 | 93 49 12 4 19.5 | 77 5656 | 58 49 8 4 19.6 | 79 5633 | 74 56 2 5 19.8 | 77 5622 | 112 56 11 5 19.9 | 77 5612 | 82 56 10 5 20.0 | 79 5602 | 81 56 11 5 20.1 | 76 5589 | 127 56 11 5 20.2 | 80 5564 | 103 56 12 5 20.3 | 69 5555 | 106 56 6 5 20.4 | 76 5555 | 99 56 33 5 20.5 | 74 5554 | 100 56 22 5 20.6 | 76 5537 | 194 56 74 5 20.8 | 75 5533 | 102 56 12 5 21.0 | 73 5527 | 110 56 48 5 21.1 | 74 5520 | 95 56 47 5 21.2 | 75 5516 | 120 56 72 5 21.3 | 79 5513 | 145 56 62 5 21.4 | 73 5497 | 84 56 29 5 21.5 | 69 5480 | 138 63 16 8 21.7 | 66 5451 | 116 63 2 8 21.8 | 65 5446 | 103 63 49 8 21.9 | 65 5442 | 87 63 4 8 22.0 | 68 5436 | 80 63 3 8 22.1 | 67 5425 | 84 63 22 8 22.2 | 57 5418 | 109 63 6 8 22.3 | 51 5408 | 126 63 6 8 22.5 | 45 5390 | 141 63 15 8 22.7 | 45 5376 | 73 63 6 8 22.8 | 43 5364 | 101 63 19 8 22.9 | 48 5362 | 94 63 23 8 23.0 | 52 5349 | 108 63 3 8 23.1 | 56 5347 | 97 63 3 8 23.3 | 55 5328 | 223 63 140 8 23.4 | 56 5323 | 85 63 32 8 23.5 | 58 5317 | 95 63 5 8 23.6 | 61 5309 | 97 67 7 10 23.8 | 57 5290 | 89 67 18 10 23.9 | 64 5280 | 119 67 21 10 24.0 | 64 5254 | 102 67 2 10 24.1 | 63 5247 | 107 67 6 10 24.2 | 62 5244 | 100 67 1 10 24.3 | 62 5238 | 104 67 4 10 24.4 | 58 5226 | 81 67 8 10 24.5 | 54 5201 | 143 67 14 10 24.7 | 42 5195 | 132 67 14 10 24.8 | 44 5189 | 100 67 8 10 24.9 | 45 5163 | 75 67 9 10 25.1 | 46 5151 | 98 67 17 10 25.2 | 50 5132 | 83 67 8 10 25.3 | 47 5124 | 115 67 13 10 25.4 | 59 5110 | 208 67 56 10 25.6 | 70 5084 | 104 71 84 10 26.0 | 81 5067 | 404 71 187 10 26.1 | 89 5059 | 109 71 60 10 26.2 | 95 5049 | 100 71 8 10 26.4 | 101 5037 | 133 71 52 10 26.5 | 108 5029 | 100 71 37 10 26.6 | 108 5026 | 105 71 36 10 26.7 | 109 5017 | 101 71 1 10 26.8 | 112 5008 | 102 71 12 10 26.9 | 76 5005 | 89 71 4 10 27.0 | 51 4999 | 131 71 9 10 27.1 | 72 4993 | 106 71 5 10 27.3 | 54 4953 | 137 71 7 10 27.5 | 59 4940 | 268 71 13 10 27.7 | 64 4921 | 137 76 9 12 27.8 | 62 4909 | 93 76 16 12 27.9 | 61 4896 | 93 76 9 12 28.0 | 64 4887 | 100 76 14 12 28.1 | 63 4869 | 122 76 3 12 28.3 | 65 4858 | 103 76 4 12 28.4 | 65 4825 | 107 76 12 12 28.5 | 65 4816 | 105 76 9 12 28.6 | 74 4800 | 101 76 5 12 28.7 | 73 4786 | 96 76 8 12 28.9 | 74 4769 | 145 76 0 12 29.0 | 79 4742 | 142 76 8 12 29.1 | 89 4731 | 121 76 16 12 29.3 | 98 4723 | 119 76 3 12 29.4 | 100 4688 | 104 76 4 12 29.5 | 107 4677 | 80 76 8 12 29.6 | 126 4655 | 80 78 12 11 29.9 | 124 4646 | 187 78 19 11 30.0 | 126 4635 | 103 78 4 11 30.1 | 130 4627 | 106 78 5 11 30.2 | 129 4614 | 103 78 6 11 30.3 | 129 4580 | 99 78 5 11 30.5 | 132 4571 | 187 78 15 11 30.6 | 130 4558 | 120 78 7 11 30.7 | 131 4547 | 95 78 7 11 30.8 | 128 4537 | 99 78 8 11 30.9 | 127 4526 | 80 78 6 11 31.1 | 126 4497 | 133 78 5 11 31.3 | 140 4427 | 193 78 7 11 31.4 | 126 4427 | 156 78 0 11 31.5 | 133 4424 | 100 78 6 11 31.6 | 125 4400 | 106 81 0 10 31.9 | 125 4392 | 234 81 10 10 32.0 | 136 4382 | 100 81 1 10 32.1 | 129 4339 | 133 81 8 10 32.2 | 141 4339 | 146 81 4 10 32.3 | 141 4329 | 99 81 13 10 32.4 | 140 4320 | 102 81 9 10 32.6 | 144 4301 | 134 81 11 10 32.7 | 140 4291 | 118 81 3 10 32.8 | 140 4271 | 88 81 7 10 32.9 | 139 4267 | 59 81 2 10 33.0 | 138 4249 | 74 81 8 10 33.1 | 141 4243 | 88 81 4 10 33.2 | 142 4227 | 78 81 8 10 33.3 | 141 4216 | 100 81 2 10 33.5 | 148 4186 | 181 81 1 10 33.6 | 160 4187 | 93 83 32 9 33.7 | 156 4190 | 68 83 16 9 33.8 | 144 4193 | 78 83 7 9 33.9 | 135 4198 | 83 83 5 9 34.0 | 118 4202 | 58 83 6 9 34.1 | 109 4208 | 71 83 19 9 34.2 | 105 4213 | 77 83 13 9 34.3 | 98 4217 | 55 83 13 9 34.4 | 93 4222 | 61 83 35 9 34.5 | 88 4226 | 83 83 61 9 34.6 | 84 4231 | 98 83 58 9 34.7 | 95 4233 | 61 83 12 9 34.8 | 106 4236 | 44 83 18 9 34.9 | 101 4239 | 40 83 14 9 35.0 | 97 4244 | 60 83 19 9 35.1 | 99 4248 | 44 83 14 9 35.2 | 99 4242 | 74 83 30 9 35.3 | 84 4144 | 87 83 3 9 35.4 | 89 4142 | 92 83 56 9 35.6 | 104 4142 | 154 80 32 11 35.7 | 113 4138 | 145 80 16 11 35.9 | 127 4138 | 175 80 14 11 36.0 | 142 4138 | 100 80 1 11 36.1 | 185 4108 | 122 80 19 11 36.2 | 188 4108 | 99 80 6 11 36.3 | 207 4109 | 82 80 45 11 36.4 | 199 4116 | 59 80 24 11 36.5 | 192 4124 | 49 80 20 11 36.6 | 195 4128 | 80 80 38 11 36.7 | 188 4131 | 64 80 20 11 36.8 | 179 4139 | 48 80 19 11 36.9 | 175 4141 | 15 80 7 11 37.0 | 171 4142 | 39 80 11 11 37.2 | 200 4054 | 129 80 29 11 37.3 | 201 4048 | 55 80 4 11 37.5 | 201 4048 | 55 80 2 11 37.6 | 203 4048 | 53 80 2 11 37.7 | 200 4048 | 59 78 0 12 37.8 | 213 3955 | 161 78 31 12 37.9 | 208 3955 | 113 78 0 12 38.0 | 218 3955 | 105 78 6 12 38.2 | 214 3947 | 100 78 0 12 38.3 | 229 3947 | 98 78 1 12 38.4 | 231 3907 | 65 78 20 12 38.5 | 223 3907 | 100 78 0 12 38.6 | 240 3846 | 100 78 24 12 38.7 | 245 3816 | 100 78 10 12 38.8 | 245 3816 | 100 78 0 12 38.9 | 255 3816 | 100 78 0 12 39.0 | 254 3816 | 100 78 3 12 39.1 | 255 3816 | 101 78 55 12 39.2 | 277 3817 | 94 78 73 12 39.3 | 295 3817 | 84 78 63 12 39.4 | 310 3817 | 18 78 14 12 39.5 | 325 3817 | 0 78 0 12 39.6 | 332 3817 | 0 78 0 12 39.7 | 351 3817 | 28 79 18 12 39.8 | 363 3818 | 48 79 35 12 39.9 | 368 3818 | 3 79 1 12 40.0 | 361 3823 | 49 79 19 12 40.1 | 350 3829 | 40 79 13 12 40.2 | 346 3831 | 15 79 7 12 40.3 | 345 3833 | 2 79 2 12 40.4 | 342 3835 | 6 79 4 12 40.5 | 340 3837 | 3 79 2 12 40.6 | 337 3839 | 5 79 4 12 40.7 | 335 3839 | 1 79 0 12 40.8 | 339 3839 | 3 79 1 12 40.9 | 338 3839 | 0 79 0 12 41.0 | 338 3839 | 0 79 0 12 41.1 | 338 3839 | 0 79 0 12 41.2 | 338 3839 | 0 79 0 12 41.3 | 337 3839 | 18 79 14 12 41.4 | 337 3839 | 16 79 14 12 41.5 | 336 3839 | 0 79 0 12 41.6 | 336 3839 | 0 79 0 12 41.7 | 336 3839 | 0 66 0 11 41.8 | 337 3839 | 0 66 0 11 41.9 | 336 3839 | 0 66 0 11 42.0 | 336 3839 | 0 66 0 11 42.1 | 333 3840 | 0 66 0 11 42.2 | 334 3840 | 0 66 0 11 42.3 | 334 3840 | 0 66 0 11 42.4 | 334 3840 | 1 66 0 11 42.5 | 334 3840 | 1 66 1 11 42.6 | 329 3840 | 0 66 0 11 42.7 | 329 3841 | 1 66 0 11 42.8 | 329 3841 | 0 66 0 11 42.9 | 329 3841 | 0 66 0 11 43.0 | 328 3841 | 0 66 0 11 43.1 | 328 3841 | 7 66 6 11 43.2 | 328 3841 | 7 66 6 11 43.3 | 327 3841 | 0 66 0 11 43.4 | 327 3841 | 0 66 0 11 43.5 | 327 3841 | 0 66 0 11 43.6 | 327 3841 | 0 54 0 9 43.7 | 327 3841 | 3 54 1 9 43.8 | 332 3841 | 0 54 0 9 43.9 | 332 3841 | 0 54 0 9 44.0 | 332 3841 | 0 54 0 9 44.1 | 329 3841 | 0 54 0 9 44.2 | 330 3841 | 0 54 0 9 44.3 | 330 3841 | 0 54 0 9 44.4 | 329 3841 | 0 54 0 9 44.5 | 329 3841 | 0 54 0 9 44.6 | 329 3841 | 0 54 0 9 44.7 | 329 3841 | 0 54 0 9 44.8 | 329 3841 | 0 54 0 9 44.9 | 329 3841 | 0 54 0 9 45.0 | 324 3843 | 12 54 5 9 45.1 | 321 3846 | 8 54 3 9 45.2 | 322 3846 | 0 54 0 9 45.3 | 322 3846 | 0 54 0 9 45.4 | 321 3846 | 0 54 0 9 45.5 | 321 3846 | 0 54 0 9 45.6 | 321 3846 | 0 45 0 7 45.7 | 322 3846 | 0 45 0 7 45.8 | 323 3846 | 0 45 0 7 45.9 | 323 3846 | 0 45 0 7 46.0 | 323 3846 | 0 45 0 7 46.1 | 321 3846 | 0 45 0 7 46.2 | 321 3846 | 0 45 0 7 46.3 | 321 3846 | 0 45 0 7 46.4 | 321 3846 | 0 45 0 7 46.5 | 320 3847 | 5 45 1 7 46.6 | 321 3847 | 0 45 0 7 earlyoom-1.6.2/contrib/oomstat/ontheedge.txt000066400000000000000000000402011374163754100212100ustar00rootroot00000000000000 | /proc/meminfo | /proc/pressure/memory Time | MemAvail SwapFree | some avg10 full avg10 s | MiB MiB | % % % % - - 0.0 | 5727 6297 | 0 0 0 0 0.1 | 5727 6297 | 0 0 0 0 0.2 | 5726 6297 | 0 0 0 0 0.3 | 5726 6297 | 0 0 0 0 0.4 | 5716 6297 | 0 0 0 0 0.5 | 5716 6297 | 0 0 0 0 0.6 | 5715 6297 | 0 0 0 0 0.7 | 5715 6297 | 0 0 0 0 0.8 | 5715 6297 | 0 0 0 0 0.9 | 5715 6297 | 0 0 0 0 1.0 | 5715 6297 | 0 0 0 0 1.1 | 5715 6297 | 0 0 0 0 1.2 | 5715 6297 | 0 0 0 0 1.3 | 5715 6297 | 0 0 0 0 1.4 | 5715 6297 | 0 0 0 0 1.5 | 5715 6297 | 0 0 0 0 1.6 | 5715 6297 | 0 0 0 0 1.7 | 5715 6297 | 0 0 0 0 1.8 | 5379 6297 | 0 0 0 0 1.9 | 4972 6297 | 3 0 0 0 2.0 | 4540 6297 | 2 0 0 0 2.1 | 4056 6297 | 0 0 0 0 2.2 | 3652 6297 | 23 0 13 0 2.3 | 3390 6297 | 50 0 28 0 2.4 | 3147 6297 | 50 0 16 0 2.5 | 2978 6297 | 47 1 23 0 2.6 | 2529 6297 | 1 1 0 0 2.7 | 2044 6297 | 0 1 0 0 2.8 | 1565 6297 | 0 1 0 0 2.9 | 1135 6297 | 0 1 0 0 3.0 | 680 6297 | 0 1 0 0 3.1 | 253 6297 | 12 1 3 0 3.2 | 184 6353 | 79 1 18 0 3.3 | 153 6364 | 89 1 15 0 3.4 | 139 6420 | 79 1 14 0 3.5 | 121 6424 | 90 1 9 0 3.6 | 122 6411 | 102 1 0 0 3.7 | 118 6401 | 90 1 10 0 3.8 | 116 6391 | 105 1 22 0 3.9 | 113 6376 | 75 1 5 0 4.1 | 115 6358 | 80 1 5 0 4.2 | 110 6346 | 86 1 9 0 4.3 | 96 6342 | 68 1 12 0 4.4 | 93 6335 | 94 1 22 0 4.5 | 93 6321 | 86 11 16 1 4.6 | 89 6308 | 62 11 4 1 4.7 | 88 6299 | 98 11 7 1 4.8 | 84 6293 | 100 11 10 1 4.9 | 77 6288 | 93 11 4 1 5.0 | 56 6278 | 82 11 3 1 5.1 | 71 6267 | 97 11 13 1 5.2 | 62 6272 | 81 11 12 1 5.3 | 52 6274 | 68 11 12 1 5.4 | 59 6272 | 91 11 12 1 5.5 | 60 6266 | 71 11 18 1 5.6 | 51 6266 | 72 11 12 1 5.7 | 46 6258 | 77 11 14 1 5.8 | 47 6247 | 63 11 1 1 5.9 | 55 6248 | 82 11 39 1 6.0 | 44 6248 | 79 11 19 1 6.1 | 51 6246 | 79 11 18 1 6.2 | 54 6243 | 74 11 19 1 6.3 | 43 6235 | 88 11 19 1 6.5 | 63 6195 | 99 23 6 3 6.6 | 65 6174 | 53 23 0 3 6.7 | 57 6174 | 50 23 0 3 6.8 | 50 6174 | 57 23 0 3 6.9 | 73 6164 | 97 23 18 3 7.0 | 103 6164 | 91 23 70 3 7.1 | 106 6165 | 77 23 39 3 7.2 | 96 6167 | 57 23 15 3 7.3 | 86 6168 | 65 23 20 3 7.4 | 75 6168 | 64 23 21 3 7.5 | 64 6168 | 71 23 19 3 7.6 | 54 6168 | 61 23 20 3 7.7 | 46 6168 | 65 23 20 3 7.8 | 56 6242 | 53 23 4 3 7.9 | 53 6241 | 46 23 11 3 8.0 | 58 6239 | 48 23 5 3 8.1 | 56 6237 | 46 23 11 3 8.2 | 57 6237 | 51 23 12 3 8.3 | 53 6236 | 45 23 6 3 8.4 | 60 6270 | 36 23 1 3 8.5 | 60 6341 | 0 29 0 5 8.6 | 59 6341 | 7 29 0 5 8.7 | 56 6342 | 23 29 0 5 8.8 | 56 6342 | 0 29 0 5 8.9 | 56 6342 | 0 29 0 5 9.0 | 53 6342 | 13 29 0 5 9.1 | 56 6339 | 59 29 0 5 9.2 | 56 6334 | 54 29 0 5 9.3 | 57 6331 | 50 29 1 5 9.4 | 57 6326 | 54 29 0 5 9.5 | 58 6322 | 66 29 0 5 9.6 | 59 6317 | 57 29 0 5 9.7 | 57 6313 | 65 29 0 5 9.8 | 59 6308 | 61 29 0 5 9.9 | 59 6311 | 49 29 2 5 10.0 | 59 6311 | 0 29 0 5 10.1 | 59 6311 | 0 29 0 5 10.2 | 59 6311 | 0 29 0 5 10.3 | 59 6311 | 0 29 0 5 10.4 | 59 6311 | 0 29 0 5 10.5 | 59 6311 | 5 29 0 4 10.6 | 58 6311 | 14 29 0 4 10.7 | 58 6311 | 0 29 0 4 10.8 | 58 6311 | 0 29 0 4 10.9 | 57 6311 | 0 29 0 4 11.0 | 57 6311 | 0 29 0 4 11.1 | 57 6311 | 0 29 0 4 11.2 | 57 6311 | 0 29 0 4 11.3 | 57 6311 | 0 29 0 4 11.4 | 56 6311 | 0 29 0 4 11.5 | 56 6311 | 0 29 0 4 11.6 | 56 6311 | 0 29 0 4 11.7 | 56 6311 | 0 29 0 4 11.8 | 56 6312 | 4 29 0 4 11.9 | 56 6312 | 0 29 0 4 12.0 | 55 6312 | 2 29 0 4 12.1 | 54 6312 | 30 29 0 4 12.2 | 62 6290 | 53 29 0 4 12.3 | 67 6290 | 33 29 0 4 12.4 | 64 6290 | 9 29 0 4 12.5 | 60 6290 | 9 25 0 3 12.6 | 58 6290 | 0 25 0 3 12.7 | 73 6290 | 49 25 0 3 12.8 | 74 6289 | 12 25 0 3 12.9 | 73 6289 | 6 25 0 3 13.0 | 70 6290 | 7 25 0 3 13.1 | 67 6290 | 8 25 0 3 13.2 | 57 6291 | 10 25 1 3 13.3 | 68 6264 | 50 25 5 3 13.4 | 73 6254 | 42 25 0 3 13.5 | 83 6254 | 40 25 0 3 13.6 | 81 6259 | 5 25 0 3 13.7 | 81 6259 | 0 25 0 3 13.8 | 81 6259 | 0 25 0 3 13.9 | 81 6259 | 0 25 0 3 14.0 | 80 6259 | 0 25 0 3 14.1 | 80 6259 | 0 25 0 3 14.2 | 78 6259 | 2 25 0 3 14.3 | 76 6259 | 2 25 0 3 14.4 | 72 6260 | 4 25 0 3 14.5 | 72 6260 | 0 22 0 3 14.6 | 72 6260 | 0 22 0 3 14.7 | 69 6260 | 7 22 0 3 14.8 | 69 6261 | 4 22 0 3 14.9 | 76 6262 | 14 22 1 3 15.0 | 76 6262 | 1 22 0 3 15.1 | 76 6262 | 0 22 0 3 15.2 | 76 6262 | 0 22 0 3 15.3 | 75 6263 | 1 22 0 3 15.4 | 75 6263 | 1 22 0 3 15.5 | 75 6263 | 0 22 0 3 15.6 | 75 6263 | 1 22 0 3 15.7 | 75 6263 | 0 22 0 3 15.8 | 75 6263 | 0 22 0 3 15.9 | 75 6263 | 0 22 0 3 16.0 | 75 6263 | 0 22 0 3 16.1 | 75 6263 | 0 22 0 3 16.2 | 74 6263 | 0 22 0 3 16.3 | 74 6263 | 0 22 0 3 16.4 | 74 6263 | 0 22 0 3 16.5 | 74 6263 | 0 18 0 2 16.6 | 74 6263 | 0 18 0 2 16.7 | 74 6263 | 0 18 0 2 16.8 | 74 6263 | 0 18 0 2 16.9 | 74 6263 | 0 18 0 2 17.0 | 74 6263 | 0 18 0 2 17.1 | 74 6263 | 1 18 0 2 17.2 | 73 6264 | 1 18 0 2 17.3 | 73 6264 | 0 18 0 2 17.4 | 73 6264 | 0 18 0 2 17.5 | 73 6264 | 0 18 0 2 17.6 | 73 6264 | 0 18 0 2 17.7 | 73 6264 | 0 18 0 2 17.8 | 73 6264 | 0 18 0 2 17.9 | 73 6264 | 0 18 0 2 18.0 | 73 6264 | 0 18 0 2 18.1 | 73 6264 | 0 18 0 2 18.2 | 73 6264 | 0 18 0 2 18.3 | 73 6264 | 0 18 0 2 18.4 | 72 6264 | 0 18 0 2 18.5 | 72 6264 | 0 15 0 2 18.6 | 73 6264 | 0 15 0 2 18.7 | 73 6264 | 0 15 0 2 18.8 | 72 6264 | 0 15 0 2 18.9 | 72 6264 | 0 15 0 2 19.0 | 72 6264 | 0 15 0 2 19.1 | 70 6264 | 0 15 0 2 19.2 | 94 6264 | 22 15 7 2 19.3 | 94 6264 | 0 15 0 2 19.4 | 94 6264 | 0 15 0 2 19.5 | 94 6264 | 0 15 0 2 19.6 | 94 6264 | 0 15 0 2 19.7 | 94 6264 | 0 15 0 2 19.8 | 94 6264 | 0 15 0 2 19.9 | 94 6264 | 0 15 0 2 20.0 | 94 6264 | 0 15 0 2 20.1 | 94 6264 | 0 15 0 2 20.2 | 94 6264 | 0 15 0 2 20.3 | 91 6265 | 11 15 0 2 20.4 | 87 6266 | 12 13 0 1 20.5 | 83 6269 | 36 13 0 1 20.6 | 78 6270 | 22 13 0 1 20.7 | 85 6269 | 30 13 3 1 20.8 | 92 6261 | 18 13 0 1 20.9 | 90 6262 | 4 13 0 1 21.0 | 89 6262 | 5 13 0 1 21.1 | 89 6262 | 0 13 0 1 21.2 | 89 6262 | 0 13 0 1 21.3 | 89 6262 | 0 13 0 1 21.4 | 89 6262 | 0 13 0 1 21.5 | 89 6262 | 0 13 0 1 21.6 | 87 6263 | 3 13 0 1 21.7 | 85 6263 | 2 13 0 1 21.8 | 84 6264 | 22 13 0 1 21.9 | 85 6265 | 4 13 0 1 22.0 | 83 6265 | 3 13 0 1 22.1 | 84 6266 | 0 13 0 1 22.2 | 84 6266 | 0 13 0 1 22.3 | 84 6266 | 0 13 0 1 22.4 | 84 6266 | 0 11 0 1 22.5 | 84 6266 | 0 11 0 1 22.6 | 84 6266 | 2 11 0 1 22.7 | 83 6266 | 0 11 0 1 22.8 | 83 6266 | 0 11 0 1 22.9 | 83 6266 | 0 11 0 1 23.0 | 82 6266 | 0 11 0 1 23.1 | 82 6266 | 0 11 0 1 23.2 | 82 6266 | 0 11 0 1 23.3 | 81 6266 | 0 11 0 1 23.4 | 81 6266 | 0 11 0 1 23.5 | 81 6266 | 1 11 0 1 23.6 | 80 6266 | 0 11 0 1 23.7 | 80 6266 | 0 11 0 1 23.8 | 80 6266 | 0 11 0 1 23.9 | 80 6266 | 0 11 0 1 24.0 | 80 6266 | 0 11 0 1 24.1 | 80 6266 | 0 11 0 1 24.2 | 80 6266 | 0 11 0 1 24.3 | 79 6266 | 0 11 0 1 24.4 | 79 6266 | 0 9 0 1 24.5 | 79 6266 | 0 9 0 1 24.6 | 79 6266 | 0 9 0 1 24.7 | 79 6266 | 0 9 0 1 24.8 | 79 6266 | 0 9 0 1 24.9 | 79 6266 | 0 9 0 1 25.0 | 79 6266 | 0 9 0 1 25.1 | 79 6266 | 0 9 0 1 25.2 | 78 6267 | 0 9 0 1 25.3 | 79 6267 | 11 9 0 1 25.4 | 95 6254 | 50 9 0 1 25.5 | 97 6255 | 53 9 0 1 25.6 | 93 6256 | 65 9 0 1 25.7 | 87 6258 | 66 9 0 1 25.8 | 79 6259 | 61 9 0 1 25.9 | 88 6257 | 72 9 1 1 26.1 | 84 6259 | 78 9 0 1 26.2 | 81 6260 | 86 9 0 1 26.3 | 90 6255 | 72 9 0 1 26.4 | 92 6250 | 98 9 0 1 26.5 | 88 6251 | 70 15 0 0 26.6 | 84 6253 | 79 15 0 0 26.7 | 82 6257 | 77 15 0 0 26.8 | 78 6260 | 52 15 0 0 26.9 | 79 6258 | 48 15 1 0 27.0 | 82 6254 | 51 15 1 0 27.1 | 88 6251 | 51 15 3 0 27.2 | 90 6250 | 61 15 0 0 27.3 | 101 6246 | 52 15 0 0 27.4 | 99 6248 | 42 15 0 0 27.5 | 96 6250 | 41 15 0 0 27.6 | 92 6250 | 34 15 0 0 27.7 | 89 6252 | 42 15 0 0 27.8 | 97 6249 | 70 15 2 0 27.9 | 106 6242 | 96 15 1 0 28.0 | 110 6240 | 66 15 0 0 28.1 | 96 6241 | 39 15 0 0 28.2 | 90 6243 | 46 15 0 0 28.3 | 100 6237 | 75 15 5 0 28.4 | 98 6240 | 51 15 0 0 28.5 | 97 6243 | 49 22 0 0 28.6 | 96 6245 | 34 22 0 0 28.7 | 92 6247 | 55 22 0 0 28.8 | 99 6248 | 37 22 0 0 28.9 | 96 6257 | 18 22 0 0 29.0 | 93 6258 | 30 22 0 0 29.1 | 91 6259 | 26 22 0 0 29.2 | 91 6247 | 42 22 9 0 29.3 | 99 6246 | 33 22 0 0 29.4 | 98 6247 | 7 22 0 0 29.5 | 97 6247 | 1 22 0 0 29.6 | 97 6247 | 3 22 0 0 29.7 | 94 6250 | 15 22 0 0 29.8 | 93 6250 | 1 22 0 0 29.9 | 93 6250 | 0 22 0 0 30.0 | 93 6251 | 2 22 0 0 30.1 | 91 6252 | 1 22 0 0 30.2 | 97 6252 | 16 22 2 0 30.3 | 99 6231 | 60 22 5 0 30.4 | 111 6218 | 50 22 0 0 30.5 | 114 6218 | 71 22 0 0 30.6 | 117 6218 | 16 22 0 0 30.7 | 119 6219 | 0 22 0 0 30.8 | 119 6219 | 0 22 0 0 30.9 | 119 6219 | 0 22 0 0 31.0 | 119 6219 | 0 22 0 0 31.1 | 118 6219 | 0 22 0 0 31.2 | 118 6219 | 0 22 0 0 31.3 | 118 6219 | 0 22 0 0 31.4 | 118 6219 | 0 22 0 0 31.5 | 118 6219 | 0 22 0 0 31.6 | 117 6219 | 31 22 0 0 31.7 | 116 6219 | 1 22 0 0 31.8 | 115 6221 | 7 22 0 0 31.9 | 114 6221 | 4 22 0 0 32.0 | 114 6221 | 0 22 0 0 32.1 | 114 6221 | 0 22 0 0 32.2 | 115 6221 | 0 22 0 0 32.3 | 115 6221 | 0 22 0 0 32.4 | 115 6221 | 0 22 0 0 32.5 | 115 6221 | 0 19 0 0 32.6 | 115 6221 | 0 19 0 0 32.7 | 115 6221 | 0 19 0 0 32.8 | 115 6221 | 0 19 0 0 32.9 | 115 6221 | 0 19 0 0 33.0 | 115 6221 | 0 19 0 0 33.1 | 115 6221 | 0 19 0 0 33.2 | 115 6221 | 0 19 0 0 33.3 | 115 6221 | 0 19 0 0 33.4 | 115 6221 | 0 19 0 0 33.5 | 115 6221 | 0 19 0 0 earlyoom-1.6.2/contrib/oomstat/oomstat.go000066400000000000000000000063401374163754100205300ustar00rootroot00000000000000package main import ( "fmt" "io/ioutil" "log" "strconv" "strings" "time" "golang.org/x/sys/unix" ) func main() { t0 := time.Now() err := unix.Mlockall(unix.MCL_CURRENT | unix.MCL_FUTURE | unix.MCL_ONFAULT) if err != nil { fmt.Printf("warning: mlockall: %v. Run as root?\n\n", err) } fmt.Println(" | /proc/meminfo | /proc/pressure/memory") fmt.Println("Time | MemAvail SwapFree | some avg10 full avg10") fmt.Println(" s | MiB MiB | % % % %") fmt.Println(" - - ") pOld := pressure() const interval = 100 for { t1 := time.Now() t := t1.Sub(t0).Seconds() p := pressure() deltaUs := p.timestamp.Sub(pOld.timestamp).Microseconds() someNow := (p.someTotal - pOld.someTotal) * 100 / deltaUs fullNow := (p.fullTotal - pOld.fullTotal) * 100 / deltaUs m := meminfo() fmt.Printf("%4.1f | %8d %8d | %4d %5d %4d %5d\n", t, m.memAvailableMiB, m.swapFreeMiB, someNow, int(p.someAvg10), fullNow, int(p.fullAvg10)) pOld = p time.Sleep(interval * time.Millisecond) } } type pressureVals struct { // percent someAvg10 float64 // microseconds someTotal int64 // percent fullAvg10 float64 // microseconds fullTotal int64 // Time when the values were read timestamp time.Time } func pressure() (p pressureVals) { /* $ cat /proc/pressure/memory some avg10=0.00 avg60=0.03 avg300=0.65 total=28851712 full avg10=0.00 avg60=0.01 avg300=0.27 total=12963374 */ buf, err := ioutil.ReadFile("/proc/pressure/memory") if err != nil { log.Fatal(err) } p.timestamp = time.Now() fields := strings.Fields(string(buf)) p.someAvg10, err = strconv.ParseFloat(fields[1][len("avg10="):], 64) if err != nil { log.Fatal(err) } p.someTotal, err = strconv.ParseInt(fields[4][len("total="):], 10, 64) if err != nil { log.Fatal(err) } p.fullAvg10, err = strconv.ParseFloat(fields[6][len("avg10="):], 64) if err != nil { log.Fatal(err) } p.fullTotal, err = strconv.ParseInt(fields[9][len("total="):], 10, 64) if err != nil { log.Fatal(err) } return } type meminfoStruct struct { memAvailableMiB int memTotalMiB int memAvailablePercent int swapFreeMiB int swapTotalMiB int swapFreePercent int } func atoi(s string) int { val, err := strconv.Atoi(s) if err != nil { log.Fatal(err) } return val } func meminfo() (m meminfoStruct) { /* $ cat /proc/meminfo MemTotal: 24537156 kB MemFree: 19759616 kB MemAvailable: 19891772 kB Buffers: 20564 kB Cached: 1029436 kB [...] SwapTotal: 1049596 kB SwapFree: 201864 kB [...] */ buf, err := ioutil.ReadFile("/proc/meminfo") if err != nil { log.Fatal(err) } fields := strings.Fields(string(buf)) for i, v := range fields { switch v { case "MemAvailable:": m.memAvailableMiB = atoi(fields[i+1]) / 1024 case "SwapFree:": m.swapFreeMiB = atoi(fields[i+1]) / 1024 case "MemTotal:": m.memTotalMiB = atoi(fields[i+1]) / 1024 case "SwapTotal:": m.swapTotalMiB = atoi(fields[i+1]) / 1024 } } m.memAvailablePercent = m.memAvailableMiB * 100 / m.memTotalMiB if m.swapTotalMiB > 0 { m.swapFreePercent = m.swapFreeMiB * 100 / m.swapTotalMiB } return } earlyoom-1.6.2/contrib/oomstat/overload.txt000066400000000000000000000566251374163754100211020ustar00rootroot00000000000000 | /proc/meminfo | /proc/pressure/memory Time | MemAvail SwapFree | some avg10 full avg10 s | MiB MiB | % % % % - - [...] 2.9 | 5902 5942 | 0 0 0 0 3.0 | 5415 5942 | 0 0 0 0 3.1 | 4930 5942 | 0 0 0 0 3.2 | 4450 5942 | 0 0 0 0 3.3 | 3969 5942 | 0 0 0 0 3.4 | 3514 5942 | 5 0 2 0 3.5 | 3065 5942 | 9 0 3 0 3.6 | 2577 5942 | 0 0 0 0 3.7 | 2105 5942 | 0 0 0 0 3.8 | 1616 5942 | 0 0 0 0 3.9 | 1143 5942 | 0 0 0 0 4.0 | 653 5942 | 0 0 0 0 4.1 | 171 5942 | 0 0 0 0 4.2 | 103 5949 | 84 0 32 0 4.3 | 86 6026 | 79 0 26 0 4.4 | 75 6049 | 98 0 24 0 4.5 | 64 6075 | 103 0 18 0 4.6 | 56 6064 | 100 0 10 0 4.7 | 51 6054 | 91 0 4 0 4.8 | 50 6045 | 88 0 4 0 4.9 | 49 6033 | 86 0 5 0 5.1 | 49 6013 | 77 0 7 0 5.2 | 41 6007 | 94 0 7 0 5.3 | 39 5996 | 91 0 8 0 5.4 | 35 5980 | 85 0 15 0 5.6 | 36 5959 | 90 10 8 1 5.7 | 40 5950 | 69 10 3 1 5.8 | 39 5941 | 94 10 4 1 5.9 | 33 5926 | 66 10 8 1 6.0 | 33 5912 | 108 10 7 1 6.1 | 35 5905 | 85 10 1 1 6.2 | 35 5893 | 85 10 8 1 6.3 | 35 5877 | 68 10 2 1 6.4 | 35 5861 | 84 10 7 1 6.5 | 33 5847 | 75 10 7 1 6.6 | 33 5832 | 74 10 6 1 6.7 | 32 5831 | 99 10 68 1 6.9 | 33 5828 | 99 10 75 1 7.0 | 33 5823 | 91 10 54 1 7.1 | 33 5819 | 109 10 78 1 7.2 | 32 5812 | 121 10 73 1 7.3 | 30 5795 | 108 10 44 1 7.4 | 26 5784 | 91 10 18 1 7.6 | 24 5775 | 88 23 6 5 7.7 | 25 5765 | 103 23 41 5 7.8 | 26 5761 | 107 23 69 5 7.9 | 25 5756 | 96 23 62 5 8.0 | 27 5747 | 86 23 43 5 8.2 | 39 5740 | 145 23 104 5 8.3 | 45 5734 | 137 23 104 5 8.4 | 44 5729 | 100 23 60 5 8.8 | 81 5621 | 650 23 301 5 9.8 | 82 5582 | 842 34 412 12 10.4 | 79 5567 | 131 34 63 12 10.5 | 78 5533 | 419 34 263 12 11.0 | 78 5496 | 420 34 205 12 11.4 | 78 5487 | 174 34 84 12 11.6 | 79 5482 | 163 44 122 18 11.8 | 78 5476 | 106 44 38 18 11.9 | 80 5471 | 101 44 57 18 12.0 | 78 5463 | 91 44 59 18 12.1 | 78 5453 | 115 44 58 18 12.2 | 78 5446 | 100 44 63 18 12.3 | 78 5431 | 150 44 96 18 12.5 | 78 5424 | 160 44 108 18 12.6 | 78 5417 | 100 44 72 18 12.7 | 78 5407 | 106 44 71 18 12.9 | 79 5402 | 138 44 101 18 13.0 | 78 5398 | 101 44 74 18 13.1 | 79 5391 | 100 44 72 18 13.2 | 81 5385 | 108 44 81 18 13.3 | 79 5379 | 121 44 89 18 13.4 | 79 5370 | 142 44 109 18 13.6 | 79 5359 | 137 54 99 27 13.7 | 79 5355 | 100 54 74 27 13.8 | 79 5353 | 104 54 72 27 13.9 | 79 5351 | 96 54 74 27 14.0 | 80 5348 | 143 54 123 27 14.2 | 80 5345 | 111 54 85 27 14.3 | 81 5337 | 129 54 97 27 14.4 | 80 5332 | 119 54 92 27 14.6 | 81 5326 | 77 54 53 27 14.7 | 81 5316 | 94 54 68 27 14.8 | 81 5310 | 89 54 67 27 14.9 | 80 5305 | 103 54 84 27 15.0 | 80 5297 | 160 54 131 27 15.1 | 82 5293 | 100 54 63 27 15.2 | 80 5289 | 119 54 89 27 15.4 | 80 5279 | 118 54 84 27 15.5 | 82 5261 | 161 54 70 27 15.6 | 82 5248 | 120 61 40 34 15.8 | 81 5242 | 100 61 72 34 15.9 | 81 5232 | 122 61 85 34 16.0 | 82 5222 | 210 61 160 34 16.2 | 82 5216 | 125 61 104 34 16.3 | 83 5208 | 201 61 172 34 16.5 | 82 5200 | 258 61 223 34 16.8 | 82 5194 | 101 61 75 34 16.9 | 81 5182 | 163 61 112 34 17.0 | 81 5164 | 165 61 104 34 17.2 | 82 5151 | 289 61 198 34 17.5 | 82 5147 | 100 61 70 34 17.6 | 82 5144 | 110 68 78 41 17.7 | 82 5141 | 126 68 95 41 17.8 | 82 5136 | 124 68 93 41 18.0 | 82 5130 | 106 68 79 41 18.1 | 82 5127 | 108 68 91 41 18.2 | 83 5122 | 91 68 68 41 18.3 | 83 5118 | 95 68 67 41 18.4 | 83 5114 | 146 68 112 41 18.6 | 83 5110 | 108 68 74 41 18.7 | 82 5101 | 89 68 66 41 18.8 | 82 5089 | 118 68 58 41 18.9 | 82 5072 | 95 68 33 41 19.0 | 83 5054 | 112 68 53 41 19.2 | 82 5028 | 151 68 71 41 19.4 | 83 5015 | 148 68 96 41 19.5 | 82 5008 | 109 71 76 44 19.7 | 83 4990 | 124 71 69 44 19.8 | 82 4978 | 163 71 111 44 20.0 | 83 4968 | 178 71 134 44 20.1 | 83 4958 | 100 71 67 44 20.2 | 82 4952 | 129 71 102 44 20.4 | 83 4944 | 135 71 88 44 20.5 | 82 4938 | 153 71 102 44 20.7 | 83 4932 | 109 71 73 44 20.8 | 82 4920 | 158 71 119 44 20.9 | 82 4911 | 121 71 83 44 21.0 | 83 4905 | 131 71 112 44 21.2 | 82 4898 | 149 71 121 44 21.3 | 82 4887 | 136 71 100 44 21.5 | 82 4879 | 103 71 62 44 21.6 | 82 4868 | 134 76 90 49 21.7 | 82 4864 | 103 76 81 49 21.8 | 82 4852 | 128 76 73 49 21.9 | 83 4842 | 118 76 73 49 22.1 | 83 4823 | 106 76 35 49 22.2 | 82 4818 | 111 76 86 49 22.3 | 82 4802 | 116 76 65 49 22.4 | 82 4798 | 106 76 83 49 22.5 | 82 4787 | 109 76 75 49 22.6 | 82 4764 | 219 76 137 49 22.8 | 83 4755 | 114 76 77 49 22.9 | 82 4744 | 100 76 56 49 23.0 | 82 4736 | 111 76 79 49 23.2 | 82 4729 | 158 76 126 49 23.3 | 83 4722 | 110 76 80 49 23.4 | 82 4715 | 107 76 73 49 23.5 | 82 4709 | 148 80 129 52 23.7 | 82 4703 | 103 80 71 52 23.8 | 82 4688 | 114 80 45 52 23.9 | 81 4677 | 106 80 61 52 24.0 | 82 4671 | 166 80 142 52 24.2 | 82 4666 | 110 80 89 52 24.3 | 82 4660 | 113 80 83 52 24.4 | 82 4651 | 140 80 99 52 24.5 | 82 4643 | 100 80 73 52 24.6 | 81 4638 | 121 80 91 52 24.8 | 81 4627 | 108 80 77 52 24.9 | 81 4623 | 104 80 87 52 25.0 | 81 4617 | 118 80 90 52 25.1 | 82 4613 | 113 80 94 52 25.2 | 82 4605 | 126 80 94 52 25.3 | 82 4597 | 111 80 80 52 25.4 | 81 4577 | 106 80 23 52 25.5 | 81 4568 | 106 84 74 55 25.7 | 81 4554 | 110 84 64 55 25.8 | 81 4543 | 140 84 110 55 25.9 | 80 4534 | 127 84 94 55 26.0 | 80 4529 | 104 84 78 55 26.1 | 80 4521 | 141 84 100 55 26.3 | 80 4514 | 104 84 74 55 26.4 | 80 4507 | 103 84 74 55 26.5 | 82 4496 | 103 84 55 55 26.6 | 79 4474 | 100 84 23 55 26.7 | 80 4454 | 104 84 18 55 26.8 | 79 4447 | 102 84 69 55 26.9 | 79 4440 | 108 84 73 55 27.0 | 79 4434 | 126 84 96 55 27.1 | 79 4428 | 111 84 81 55 27.2 | 1 4421 | 106 84 54 55 27.3 | 6 4415 | 166 84 124 55 27.5 | 12 4408 | 148 87 103 57 27.7 | 21 4396 | 349 87 266 57 28.0 | 27 4388 | 187 87 133 57 28.2 | 39 4376 | 171 87 76 57 28.4 | 59 4354 | 463 87 297 57 28.8 | 63 4349 | 124 87 91 57 29.0 | 74 4332 | 190 87 152 57 29.4 | 75 4326 | 403 87 299 57 29.6 | 75 4317 | 105 89 63 59 29.7 | 75 4302 | 152 89 90 59 29.8 | 74 4296 | 108 89 70 59 29.9 | 75 4282 | 138 89 88 59 30.1 | 75 4277 | 114 89 74 59 30.2 | 74 4257 | 160 89 88 59 30.3 | 74 4249 | 101 89 58 59 30.4 | 74 4244 | 114 89 83 59 30.5 | 75 4236 | 114 89 75 59 30.7 | 75 4227 | 106 89 63 59 30.8 | 74 4218 | 117 89 88 59 30.9 | 74 4212 | 174 89 138 59 31.1 | 75 4205 | 104 89 70 59 31.2 | 75 4198 | 116 89 84 59 31.3 | 74 4194 | 118 89 89 59 31.4 | 74 4185 | 103 89 74 59 31.5 | 74 4172 | 109 89 70 59 31.6 | 75 4159 | 120 91 52 60 31.7 | 75 4150 | 121 91 80 60 31.9 | 75 4140 | 275 91 226 60 32.1 | 75 4134 | 104 91 71 60 32.2 | 74 4129 | 106 91 79 60 32.3 | 75 4123 | 122 91 98 60 32.5 | 75 4116 | 111 91 81 60 32.6 | 75 4110 | 107 91 80 60 32.7 | 74 4087 | 234 91 142 60 32.9 | 74 4080 | 101 91 70 60 33.0 | 76 4060 | 106 91 63 60 33.1 | 75 4051 | 117 91 89 60 33.2 | 75 4043 | 104 91 68 60 33.3 | 76 4032 | 105 91 60 60 33.5 | 12 4024 | 129 91 77 60 33.6 | 17 4017 | 220 92 176 62 33.8 | 23 4009 | 168 92 118 62 34.0 | 27 4002 | 169 92 125 62 34.1 | 27 3987 | 154 92 104 62 34.4 | 24 3975 | 162 92 119 62 34.5 | 28 3967 | 128 92 93 62 34.6 | 33 3961 | 100 92 60 62 34.7 | 39 3952 | 178 92 127 62 34.9 | 47 3942 | 194 92 123 62 35.1 | 52 3933 | 109 92 63 62 35.2 | 57 3927 | 196 92 147 62 35.4 | 59 3924 | 126 92 109 62 35.5 | 63 3919 | 139 92 104 62 35.6 | 69 3911 | 100 94 66 63 35.8 | 71 3907 | 191 94 149 63 35.9 | 75 3901 | 244 94 200 63 36.2 | 75 3893 | 111 94 74 63 36.3 | 75 3885 | 117 94 76 63 36.4 | 7 3857 | 701 94 499 63 37.1 | 17 3833 | 539 94 389 63 37.7 | 15 3815 | 259 95 197 65 37.9 | 17 3810 | 164 95 139 65 38.1 | 17 3799 | 122 95 68 65 58.3 | 76 2612 | 20261 99 14409 70 58.4 | 74 2606 | 119 99 95 70 58.6 | 76 2602 | 105 99 76 70 58.7 | 76 2595 | 122 99 89 70 58.8 | 75 2586 | 102 99 59 70 58.9 | 76 2578 | 118 99 80 70 59.0 | 75 2569 | 101 99 69 70 59.1 | 77 2551 | 109 99 38 70 59.2 | 77 2550 | 101 99 77 70 59.3 | 76 2536 | 127 99 50 70 59.5 | 76 2532 | 140 99 106 70 59.6 | 76 2515 | 116 99 57 69 59.7 | 76 2505 | 124 99 88 69 59.8 | 75 2497 | 116 99 93 69 60.0 | 76 2492 | 100 99 82 69 60.1 | 77 2489 | 121 99 96 69 60.2 | 76 2486 | 125 99 107 69 60.3 | 76 2480 | 107 99 81 69 60.4 | 76 2474 | 102 99 75 69 60.5 | 76 2465 | 140 99 112 69 60.7 | 76 2461 | 116 99 97 69 60.8 | 76 2451 | 103 99 70 69 60.9 | 76 2444 | 164 99 131 69 61.0 | 76 2437 | 107 99 80 69 61.1 | 76 2429 | 100 99 74 69 61.2 | 76 2424 | 113 99 83 69 61.4 | 77 2419 | 117 99 89 69 61.5 | 77 2411 | 149 99 114 69 61.6 | 76 2407 | 102 99 80 70 61.7 | 76 2401 | 162 99 145 70 61.9 | 76 2394 | 100 99 80 70 62.0 | 76 2390 | 111 99 88 70 62.1 | 76 2381 | 182 99 150 70 62.3 | 76 2375 | 135 99 101 70 62.4 | 76 2365 | 107 99 56 70 62.5 | 76 2352 | 106 99 70 70 62.6 | 76 2346 | 100 99 73 70 62.7 | 76 2338 | 187 99 138 70 62.9 | 76 2334 | 118 99 96 70 63.0 | 76 2328 | 123 99 94 70 63.2 | 76 2320 | 105 99 80 70 63.3 | 76 2308 | 203 99 153 70 63.5 | 76 2299 | 104 99 58 70 63.6 | 76 2295 | 107 99 86 71 63.8 | 76 2288 | 249 99 216 71 63.9 | 76 2272 | 103 99 57 71 64.0 | 76 2267 | 107 99 78 71 64.1 | 76 2259 | 176 99 146 71 64.3 | 76 2258 | 100 99 34 71 64.4 | 76 2251 | 130 99 83 71 64.6 | 76 2237 | 100 99 67 71 64.7 | 75 2228 | 100 99 78 71 64.8 | 75 2219 | 100 99 81 71 64.9 | 76 2209 | 100 99 78 71 65.0 | 76 2195 | 100 99 71 71 65.1 | 76 2188 | 100 99 85 71 65.2 | 76 2172 | 100 99 72 71 65.3 | 76 2167 | 100 99 75 71 65.4 | 76 2162 | 100 99 72 71 65.5 | 76 2156 | 100 99 80 71 65.6 | 78 2149 | 119 99 98 71 65.7 | 76 2134 | 108 99 70 71 65.8 | 76 2128 | 101 99 82 71 65.9 | 76 2120 | 101 99 80 71 66.0 | 79 2112 | 114 99 90 71 66.1 | 76 2097 | 100 99 78 71 66.2 | 79 2093 | 119 99 102 71 66.4 | 79 2069 | 133 99 92 71 66.5 | 78 2029 | 100 99 50 71 66.6 | 76 2019 | 100 99 75 71 66.7 | 77 2014 | 109 99 92 71 66.8 | 76 1989 | 100 99 63 71 66.9 | 78 1983 | 100 99 77 71 67.0 | 77 1957 | 146 99 87 71 67.1 | 76 1949 | 100 99 70 71 67.2 | 76 1943 | 100 99 83 71 67.3 | 79 1939 | 101 99 85 71 67.4 | 76 1918 | 100 99 58 71 67.5 | 76 1908 | 100 99 76 71 67.6 | 78 1891 | 114 99 84 71 67.7 | 77 1886 | 100 99 85 71 67.8 | 78 1855 | 101 99 45 71 67.9 | 76 1832 | 100 99 57 71 68.0 | 76 1816 | 100 99 67 71 68.1 | 76 1811 | 100 99 81 71 68.2 | 76 1805 | 100 99 84 71 68.3 | 76 1799 | 100 99 83 71 68.4 | 76 1776 | 100 99 62 71 68.5 | 76 1751 | 100 99 61 71 68.6 | 77 1736 | 100 99 72 71 68.7 | 78 1723 | 129 99 92 71 68.9 | 76 1714 | 100 99 86 71 69.0 | 77 1706 | 100 99 80 71 69.1 | 77 1698 | 100 99 80 71 69.2 | 79 1685 | 100 99 64 71 69.3 | 81 1674 | 100 99 75 71 69.4 | 81 1660 | 100 99 71 71 69.5 | 81 1647 | 100 99 72 71 69.6 | 81 1635 | 101 99 74 71 69.7 | 81 1622 | 109 99 78 71 69.8 | 81 1609 | 100 99 71 71 69.9 | 80 1598 | 100 99 77 71 70.0 | 80 1589 | 100 99 81 71 70.1 | 80 1580 | 100 99 78 71 70.2 | 80 1568 | 100 99 73 71 70.3 | 84 1553 | 149 99 110 71 70.4 | 81 1542 | 101 99 84 71 70.5 | 81 1532 | 100 99 79 71 70.6 | 81 1524 | 100 99 80 71 70.7 | 81 1510 | 100 99 70 71 70.8 | 81 1482 | 265 99 190 71 71.1 | 81 1478 | 100 99 70 71 71.2 | 83 1474 | 110 99 85 71 71.3 | 81 1462 | 100 99 63 71 71.4 | 81 1460 | 100 99 82 71 71.5 | 83 1452 | 104 99 78 71 71.6 | 82 1439 | 100 99 63 72 71.7 | 84 1418 | 101 99 60 72 71.8 | 83 1397 | 104 99 65 72 71.9 | 84 1375 | 112 99 73 72 72.0 | 83 1365 | 100 99 76 72 72.1 | 84 1349 | 100 99 67 72 72.2 | 85 1340 | 102 99 78 72 72.3 | 84 1319 | 100 99 59 72 72.4 | 84 1307 | 100 99 73 72 72.5 | 85 1286 | 102 99 68 72 72.6 | 85 1274 | 100 99 76 72 72.7 | 84 1254 | 100 99 60 72 72.8 | 84 1238 | 100 99 73 72 72.9 | 84 1229 | 100 99 83 72 73.0 | 84 1217 | 100 99 72 72 73.1 | 85 1206 | 100 99 76 72 73.2 | 84 1199 | 100 99 82 72 73.3 | 84 1184 | 100 99 72 72 73.4 | 84 1173 | 100 99 69 72 73.5 | 85 1168 | 100 99 83 72 73.6 | 86 1163 | 100 99 87 72 73.7 | 88 1153 | 100 99 72 72 73.8 | 91 1138 | 100 99 84 72 73.9 | 91 1131 | 100 99 72 72 74.0 | 91 1121 | 100 99 75 72 74.1 | 92 1113 | 100 99 72 72 74.2 | 92 1107 | 100 99 86 72 74.3 | 92 1094 | 101 99 74 72 74.4 | 94 1083 | 102 99 73 72 74.5 | 93 1073 | 100 99 79 72 74.6 | 95 1052 | 100 99 69 72 74.8 | 97 1030 | 130 99 71 72 74.9 | 96 994 | 100 99 39 72 75.0 | 95 976 | 100 99 46 72 75.1 | 95 962 | 100 99 55 72 75.2 | 96 951 | 100 99 70 72 75.3 | 100 930 | 100 99 18 72 75.4 | 98 901 | 100 99 52 72 75.5 | 98 880 | 101 99 56 72 75.6 | 102 847 | 100 99 32 70 75.7 | 103 841 | 100 99 67 70 75.8 | 105 821 | 107 99 60 70 75.9 | 104 810 | 100 99 72 70 76.0 | 105 806 | 100 99 79 70 76.1 | 107 795 | 100 99 76 70 76.2 | 107 786 | 100 99 73 70 76.3 | 107 777 | 100 99 76 70 76.4 | 109 774 | 100 99 75 70 76.5 | 110 768 | 100 99 80 70 76.6 | 112 761 | 100 99 81 70 76.7 | 119 750 | 129 99 97 70 76.8 | 116 733 | 104 99 74 70 76.9 | 122 724 | 130 99 89 70 77.1 | 122 715 | 100 99 69 70 77.2 | 123 710 | 100 99 81 70 77.3 | 125 704 | 100 99 79 70 77.4 | 127 699 | 100 99 79 70 77.5 | 129 689 | 100 99 75 70 77.6 | 131 685 | 100 99 77 70 77.7 | 136 675 | 118 99 86 70 77.8 | 138 663 | 100 99 63 70 77.9 | 142 650 | 103 99 65 70 78.0 | 146 629 | 103 99 50 70 78.1 | 147 624 | 100 99 63 70 78.2 | 149 604 | 100 99 66 70 78.3 | 151 600 | 100 99 80 70 78.4 | 147 579 | 100 99 65 70 78.5 | 149 558 | 100 99 61 70 78.6 | 147 551 | 100 99 84 70 78.7 | 148 520 | 109 99 51 70 78.8 | 148 502 | 103 99 69 70 78.9 | 148 472 | 102 99 58 70 79.0 | 147 453 | 100 99 63 70 79.1 | 147 444 | 100 99 79 70 79.2 | 147 434 | 100 99 80 70 79.3 | 147 420 | 109 99 73 70 79.4 | 151 411 | 100 99 67 70 79.5 | 147 388 | 100 99 58 70 79.6 | 150 383 | 106 99 87 70 79.7 | 147 363 | 100 99 59 70 79.8 | 148 357 | 109 99 87 70 79.9 | 148 339 | 100 99 69 70 80.0 | 148 339 | 100 99 97 70 80.1 | 148 332 | 100 99 80 70 80.2 | 149 317 | 116 99 85 70 80.3 | 148 305 | 122 99 99 70 80.5 | 150 293 | 131 99 95 70 80.6 | 148 279 | 100 99 73 70 # system hangs here 174.3 | 1239 18 | 93694 100 75743 80 174.4 | 1228 597 | 125 100 0 80 174.5 | 1255 1434 | 108 100 0 80 174.6 | 1320 2144 | 69 100 0 80 174.7 | 1460 2724 | 56 100 0 80 174.8 | 1745 3271 | 100 100 0 80 175.0 | 2742 4323 | 222 100 0 80 175.2 | 3286 4797 | 110 100 0 80 175.3 | 3973 5188 | 75 100 0 80 175.4 | 5011 5547 | 51 100 0 80 175.5 | 6206 5702 | 57 100 7 80 175.6 | 6492 5704 | 85 100 25 80 175.7 | 6486 5706 | 90 98 39 70 175.8 | 6481 5707 | 93 98 36 70 175.9 | 6476 5709 | 99 98 47 70 176.0 | 6473 5712 | 100 98 41 70 176.1 | 6469 5714 | 88 98 38 70 176.2 | 6464 5716 | 93 98 40 70 176.3 | 6461 5717 | 101 98 50 70 176.4 | 6457 5719 | 84 98 41 70 176.5 | 6454 5721 | 99 98 45 70 176.6 | 6451 5722 | 95 98 46 70 176.7 | 6435 5724 | 82 98 32 70 176.8 | 6431 5726 | 69 98 36 70 176.9 | 6429 5728 | 87 98 25 70 177.0 | 6423 5731 | 99 98 19 70 177.1 | 6416 5734 | 95 98 0 70 177.2 | 6411 5738 | 128 98 16 70 177.3 | 6406 5741 | 99 98 13 70 177.4 | 6402 5743 | 101 98 8 70 177.5 | 6399 5745 | 62 98 9 70 177.6 | 6395 5747 | 75 98 12 70 177.7 | 6391 5749 | 81 96 22 62 177.8 | 6389 5751 | 85 96 33 62 177.9 | 6389 5756 | 90 96 25 62 earlyoom-1.6.2/contrib/utf8_membomb.sh000077500000000000000000000007161374163754100177510ustar00rootroot00000000000000#!/bin/bash # Testcase for https://github.com/rfjakob/earlyoom/issues/110 # # earlyoom output should look like this: # # sending SIGTERM to process 28570 "tail_😀😀": badness 629, VmRSS 15048 MiB # # and not like this: # # sending SIGTERM to process 28491 "tail_😀😀�": badness 630, VmRSS 15076 MiB set -eu cd $(mktemp -d) TAIL=$(which tail) ln -s $TAIL tail_😀😀😀😀😀😀😀😀 exec ./tail_😀😀😀😀😀😀😀😀 /dev/zero earlyoom-1.6.2/contrib/zombie.c000066400000000000000000000012601374163754100164520ustar00rootroot00000000000000/* Creates a zombie child process. Automatically exits after 10 minutes. Should look like this in ps: $ ps auxwwww | grep zombie jakob 7513 0.0 0.0 2172 820 pts/1 S+ 13:44 0:00 ./zombie jakob 7514 0.0 0.0 0 0 pts/1 Z+ 13:44 0:00 [zombie] */ #include #include #include #include #include int main() { pid_t pid = fork(); if (pid < 0) { perror("fork failed"); exit(1); } if (pid > 0) { /* parent */ sleep(600); int wstatus; wait(&wstatus); exit(0); } else { /* child */ exit(0); } } earlyoom-1.6.2/earlyoom.default000066400000000000000000000010641374163754100165600ustar00rootroot00000000000000# Default settings for earlyoom. This file is sourced by /bin/sh from # /etc/init.d/earlyoom or by systemd from earlyoom.service. # Options to pass to earlyoom EARLYOOM_ARGS="-r 3600" # Examples: # Print memory report every second instead of every minute # EARLYOOM_ARGS="-r 1" # Available minimum memory 5% # EARLYOOM_ARGS="-m 5" # Available minimum memory 15% and free minimum swap 5% # EARLYOOM_ARGS="-m 15 -s 5" # Avoid killing processes whose name matches this regexp # EARLYOOM_ARGS="--avoid '(^|/)(init|X|sshd|firefox)$'" # See more at `earlyoom -h' earlyoom-1.6.2/earlyoom.initscript.in000077500000000000000000000106641374163754100177420ustar00rootroot00000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: earlyoom # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Early OOM Killer # Description: A userspace service that will kill the largest process # (by VmRSS residential size) when free RAM drops below 10%. ### END INIT INFO # Author: https://github.com/rfjakob # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="Early OOM Daemon" NAME=earlyoom DAEMON=:TARGET:/$NAME #DAEMON_ARGS="--options args" LOGFILE=/var/log/$NAME.log PIDFILE=/var/run/$NAME.pid SCRIPTNAME=:SYSCONFDIR:/init.d/$NAME # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r :SYSCONFDIR:/default/$NAME ] && . :SYSCONFDIR:/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.2-14) to ensure that this file is present # and status_of_proc is working. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec /bin/bash --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --background --pidfile $PIDFILE --exec /bin/bash -- -c "exec $DAEMON $EARLYOOM_ARGS 2> \"$LOGFILE\"" \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. # # We may be able to skip this, if we are not concerned about being attached # to any resources. # This was blocking until the timeout, so I replaced 0/ with TERM/ and now # it completes immediately. start-stop-daemon --stop --quiet --oknodo --retry=TERM/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } # # Function that sends a SIGHUP to the daemon/service # #do_reload() { # # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # #start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME #return 0 #} case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; #reload|force-reload) # # If do_reload() is not implemented then leave this commented out # and leave 'force-reload' as an alias for 'restart'. # #log_daemon_msg "Reloading $DESC" "$NAME" #do_reload #log_end_msg $? #;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac : earlyoom-1.6.2/earlyoom.service.in000066400000000000000000000013071374163754100172010ustar00rootroot00000000000000[Unit] Description=Early OOM Daemon Documentation=man:earlyoom(1) https://github.com/rfjakob/earlyoom [Service] EnvironmentFile=-:SYSCONFDIR:/default/earlyoom ExecStart=:TARGET:/earlyoom $EARLYOOM_ARGS # Run as an unprivileged user with random user id DynamicUser=true # Allow killing processes and calling mlockall() AmbientCapabilities=CAP_KILL CAP_IPC_LOCK # We don't need write access anywhere ProtectSystem=strict # We don't need /home at all, make it inaccessible ProtectHome=true # earlyoom never exits on it's own, so have systemd # restart it should it get killed for some reason. Restart=always # set memory limits and max tasks number TasksMax=10 MemoryMax=50M [Install] WantedBy=multi-user.target earlyoom-1.6.2/globals.c000066400000000000000000000000261374163754100151470ustar00rootroot00000000000000int enable_debug = 0; earlyoom-1.6.2/globals.h000066400000000000000000000001511374163754100151530ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef GLOBALS_H #define GLOBALS_H extern int enable_debug; #endif earlyoom-1.6.2/kill.c000066400000000000000000000236351374163754100144720ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* Kill the most memory-hungy process */ #include #include #include #include // for PATH_MAX #include #include #include #include #include #include #include "globals.h" #include "kill.h" #include "meminfo.h" #include "msg.h" #define BADNESS_PREFER 300 #define BADNESS_AVOID -300 static int isnumeric(char* str) { int i = 0; // Empty string is not numeric if (str[0] == 0) return 0; while (1) { if (str[i] == 0) // End of string return 1; if (isdigit(str[i]) == 0) return 0; i++; } } static void notify(const char* summary, const char* body) { int pid = fork(); if (pid > 0) { // parent return; } char summary2[1024] = { 0 }; snprintf(summary2, sizeof(summary2), "string:%s", summary); char body2[1024] = "string:"; if (body != NULL) { snprintf(body2, sizeof(body2), "string:%s", body); } // Complete command line looks like this: // dbus-send --system / net.nuetzlich.SystemNotifications.Notify 'string:summary text' 'string:and body text' execl("/usr/bin/dbus-send", "dbus-send", "--system", "/", "net.nuetzlich.SystemNotifications.Notify", summary2, body2, NULL); warn("notify: exec failed: %s\n", strerror(errno)); exit(1); } /* * Send the selected signal to "pid" and wait for the process to exit * (max 10 seconds) */ int kill_wait(const poll_loop_args_t* args, pid_t pid, int sig) { if (args->dryrun && sig != 0) { warn("dryrun, not actually sending any signal\n"); return 0; } meminfo_t m = { 0 }; const unsigned poll_ms = 100; int res = kill(pid, sig); if (res != 0) { return res; } /* signal 0 does not kill the process. Don't wait for it to exit */ if (sig == 0) { return 0; } for (unsigned i = 0; i < 100; i++) { float secs = (float)(i * poll_ms) / 1000; // We have sent SIGTERM but now have dropped below SIGKILL limits. // Escalate to SIGKILL. if (sig != SIGKILL) { m = parse_meminfo(); print_mem_stats(debug, m); if (m.MemAvailablePercent <= args->mem_kill_percent && m.SwapFreePercent <= args->swap_kill_percent) { sig = SIGKILL; res = kill(pid, sig); // kill first, print after warn("escalating to SIGKILL after %.1f seconds\n", secs); if (res != 0) { return res; } } } else if (enable_debug) { m = parse_meminfo(); print_mem_stats(printf, m); } if (!is_alive(pid)) { warn("process exited after %.1f seconds\n", secs); return 0; } struct timespec req = { .tv_sec = (time_t)(poll_ms / 1000), .tv_nsec = (poll_ms % 1000) * 1000000 }; nanosleep(&req, NULL); } errno = ETIME; return -1; } /* * Find the process with the largest oom_score. */ procinfo_t find_largest_process(const poll_loop_args_t* args) { DIR* procdir = opendir("/proc"); if (procdir == NULL) { fatal(5, "%s: could not open /proc: %s", __func__, strerror(errno)); } struct timespec t0 = { 0 }, t1 = { 0 }; if (enable_debug) { clock_gettime(CLOCK_MONOTONIC, &t0); } procinfo_t victim = { 0 }; while (1) { errno = 0; struct dirent* d = readdir(procdir); if (d == NULL) { if (errno != 0) warn("%s: readdir error: %s", __func__, strerror(errno)); break; } // proc contains lots of directories not related to processes, // skip them if (!isnumeric(d->d_name)) continue; procinfo_t cur = { .pid = (int)strtol(d->d_name, NULL, 10), .uid = -1, .badness = -1, .VmRSSkiB = -1, }; if (cur.pid <= 1) // Let's not kill init. continue; debug("pid %5d:", cur.pid); { int res = get_oom_score(cur.pid); if (res < 0) { debug(" error reading oom_score: %s\n", strerror(-res)); continue; } cur.badness = res; } if (args->ignore_oom_score_adj) { int oom_score_adj = 0; int res = get_oom_score_adj(cur.pid, &oom_score_adj); if (res < 0) { debug(" error reading oom_score_adj: %s\n", strerror(-res)); continue; } if (oom_score_adj > 0) { cur.badness -= oom_score_adj; } } if ((args->prefer_regex || args->avoid_regex)) { int res = get_comm(cur.pid, cur.name, sizeof(cur.name)); if (res < 0) { debug(" error reading process name: %s\n", strerror(-res)); continue; } if (args->prefer_regex && regexec(args->prefer_regex, cur.name, (size_t)0, NULL, 0) == 0) { cur.badness += BADNESS_PREFER; } if (args->avoid_regex && regexec(args->avoid_regex, cur.name, (size_t)0, NULL, 0) == 0) { cur.badness += BADNESS_AVOID; } } debug(" badness %3d", cur.badness); if (cur.badness < victim.badness) { // skip "type 1", encoded as 1 space debug(" \n"); continue; } { long long res = get_vm_rss_kib(cur.pid); if (res < 0) { debug(" error reading rss: %s\n", strerror((int)-res)); continue; } cur.VmRSSkiB = res; } debug(" vm_rss %7llu", cur.VmRSSkiB); if (cur.VmRSSkiB == 0) { // Kernel threads have zero rss // skip "type 2", encoded as 2 spaces debug(" \n"); continue; } if (cur.badness == victim.badness && cur.VmRSSkiB <= victim.VmRSSkiB) { // skip "type 3", encoded as 3 spaces debug(" \n"); continue; } // Skip processes with oom_score_adj = -1000, like the // kernel oom killer would. int oom_score_adj = 0; { int res = get_oom_score_adj(cur.pid, &oom_score_adj); if (res < 0) { debug(" error reading oom_score_adj: %s\n", strerror(-res)); continue; } if (oom_score_adj == -1000) { // skip "type 4", encoded as 3 spaces debug(" \n"); continue; } } // Fill out remaining fields if (strlen(cur.name) == 0) { int res = get_comm(cur.pid, cur.name, sizeof(cur.name)); if (res < 0) { debug(" error reading process name: %s\n", strerror(-res)); continue; } } { int res = get_uid(cur.pid); if (res < 0) { debug(" error reading uid: %s\n", strerror(-res)); continue; } cur.uid = res; } // Save new victim victim = cur; debug(" uid %4d oom_score_adj %4d \"%s\" <--- new victim\n", victim.uid, oom_score_adj, victim.name); } // end of while(1) loop closedir(procdir); if (enable_debug) { clock_gettime(CLOCK_MONOTONIC, &t1); long delta = (t1.tv_sec - t0.tv_sec) * 1000000 + (t1.tv_nsec - t0.tv_nsec) / 1000; debug("selecting victim took %ld.%03ld ms\n", delta / 1000, delta % 1000); } if (victim.pid == getpid()) { warn("%s: selected myself (pid %d). Do you use hidpid? See https://github.com/rfjakob/earlyoom/wiki/proc-hidepid\n", __func__, victim.pid); // zeroize victim struct victim = (const procinfo_t) { 0 }; } return victim; } /* * Kill the victim process, wait for it to exit, send a gui notification * (if enabled). */ void kill_process(const poll_loop_args_t* args, int sig, const procinfo_t victim) { if (victim.pid <= 0) { warn("Could not find a process to kill. Sleeping 1 second.\n"); if (args->notify) { notify("earlyoom", "Error: Could not find a process to kill. Sleeping 1 second."); } sleep(1); return; } char* sig_name = "?"; if (sig == SIGTERM) { sig_name = "SIGTERM"; } else if (sig == SIGKILL) { sig_name = "SIGKILL"; } else if (sig == 0) { sig_name = "0 (no-op signal)"; } // sig == 0 is used as a self-test during startup. Don't notifiy the user. if (sig != 0 || enable_debug) { warn("sending %s to process %d uid %d \"%s\": badness %d, VmRSS %lld MiB\n", sig_name, victim.pid, victim.uid, victim.name, victim.badness, victim.VmRSSkiB / 1024); } int res = kill_wait(args, victim.pid, sig); int saved_errno = errno; // Send the GUI notification AFTER killing a process. This makes it more likely // that there is enough memory to spawn the notification helper. if (sig != 0) { char notif_args[PATH_MAX + 1000]; snprintf(notif_args, sizeof(notif_args), "Low memory! Killing process %d %s", victim.pid, victim.name); if (args->notify) { notify("earlyoom", notif_args); } } if (sig == 0) { return; } if (res != 0) { warn("kill failed: %s\n", strerror(saved_errno)); if (args->notify) { notify("earlyoom", "Error: Failed to kill process"); } // Killing the process may have failed because we are not running as root. // In that case, trying again in 100ms will just yield the same error. // Throttle ourselves to not spam the log. if (saved_errno == EPERM) { warn("sleeping 1 second\n"); sleep(1); } } } earlyoom-1.6.2/kill.h000066400000000000000000000016171374163754100144730ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef KILL_H #define KILL_H #include #include #include "meminfo.h" typedef struct { /* if the available memory AND swap goes below these percentages, * we start killing processes */ double mem_term_percent; double mem_kill_percent; double swap_term_percent; double swap_kill_percent; /* ignore /proc/PID/oom_score_adj? */ bool ignore_oom_score_adj; /* send d-bus notifications? */ bool notify; /* prefer/avoid killing these processes. NULL = no-op. */ regex_t* prefer_regex; regex_t* avoid_regex; /* memory report interval, in milliseconds */ int report_interval_ms; /* Flag --dryrun was passed */ bool dryrun; } poll_loop_args_t; void kill_process(const poll_loop_args_t* args, int sig, procinfo_t victim); procinfo_t find_largest_process(const poll_loop_args_t* args); #endif earlyoom-1.6.2/main.c000066400000000000000000000400471374163754100144570ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* Check available memory and swap in a loop and start killing * processes if they get too low */ #include #include #include #include #include #include #include #include #include #include #include #include "globals.h" #include "kill.h" #include "meminfo.h" #include "msg.h" /* Don't fail compilation if the user has an old glibc that * does not define MCL_ONFAULT. The kernel may still be recent * enough to support the flag. */ #ifndef MCL_ONFAULT #define MCL_ONFAULT 4 #endif #ifndef VERSION #define VERSION "*** unknown version ***" #endif /* Arbitrary identifiers for long options that do not have a short * version */ enum { LONG_OPT_PREFER = 513, LONG_OPT_AVOID, LONG_OPT_DRYRUN, }; static int set_oom_score_adj(int); static void poll_loop(const poll_loop_args_t* args); // Prevent Golang / Cgo name collision when the test suite runs - // Cgo generates it's own main function. #ifdef CGO #define main main2 #endif double min(double x, double y) { if (x < y) return x; return y; } void handle_sigchld(int sig) { (void)sig; // unused waitpid(-1, NULL, WNOHANG); } int main(int argc, char* argv[]) { poll_loop_args_t args = { .mem_term_percent = 10, .swap_term_percent = 10, .mem_kill_percent = 5, .swap_kill_percent = 5, .report_interval_ms = 1000, /* omitted fields are set to zero */ }; int set_my_priority = 0; char* prefer_cmds = NULL; char* avoid_cmds = NULL; regex_t _prefer_regex; regex_t _avoid_regex; /* request line buffering for stdout - otherwise the output * may lag behind stderr */ setlinebuf(stdout); /* clean up dbus-send zombies */ signal(SIGCHLD, handle_sigchld); fprintf(stderr, "earlyoom " VERSION "\n"); if (chdir("/proc") != 0) { fatal(4, "Could not cd to /proc: %s", strerror(errno)); } meminfo_t m = parse_meminfo(); int c; const char* short_opt = "m:s:M:S:kinN:dvr:ph"; struct option long_opt[] = { { "prefer", required_argument, NULL, LONG_OPT_PREFER }, { "avoid", required_argument, NULL, LONG_OPT_AVOID }, { "dryrun", no_argument, NULL, LONG_OPT_DRYRUN }, { "help", no_argument, NULL, 'h' }, { 0, 0, NULL, 0 } /* end-of-array marker */ }; bool have_m = 0, have_M = 0, have_s = 0, have_S = 0; double mem_term_kib = 0, mem_kill_kib = 0, swap_term_kib = 0, swap_kill_kib = 0; while ((c = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { float report_interval_f = 0; term_kill_tuple_t tuple; switch (c) { case -1: /* no more arguments */ case 0: /* long option toggles */ break; case 'm': // Use 99 as upper limit. Passing "-m 100" makes no sense. tuple = parse_term_kill_tuple(optarg, 99); if (strlen(tuple.err)) { fatal(15, "-m: %s", tuple.err); } args.mem_term_percent = tuple.term; args.mem_kill_percent = tuple.kill; have_m = 1; break; case 's': // Using "-s 100" is a valid way to ignore swap usage tuple = parse_term_kill_tuple(optarg, 100); if (strlen(tuple.err)) { fatal(16, "-s: %s", tuple.err); } args.swap_term_percent = tuple.term; args.swap_kill_percent = tuple.kill; have_s = 1; break; case 'M': tuple = parse_term_kill_tuple(optarg, m.MemTotalKiB * 100 / 99); if (strlen(tuple.err)) { fatal(15, "-M: %s", tuple.err); } mem_term_kib = tuple.term; mem_kill_kib = tuple.kill; have_M = 1; break; case 'S': tuple = parse_term_kill_tuple(optarg, m.SwapTotalKiB * 100 / 99); if (strlen(tuple.err)) { fatal(16, "-S: %s", tuple.err); } if (m.SwapTotalKiB == 0) { warn("warning: -S: total swap is zero, using default percentages\n"); break; } swap_term_kib = tuple.term; swap_kill_kib = tuple.kill; have_S = 1; break; case 'k': fprintf(stderr, "Option -k is ignored since earlyoom v1.2\n"); break; case 'i': args.ignore_oom_score_adj = 1; fprintf(stderr, "Ignoring positive oom_score_adj values (-i)\n"); break; case 'n': args.notify = true; fprintf(stderr, "Notifying through D-Bus\n"); break; case 'N': args.notify = true; fprintf(stderr, "Notifying through D-Bus, argument '%s' ignored for compatability\n", optarg); break; case 'd': enable_debug = 1; break; case 'v': // The version has already been printed above exit(0); case 'r': report_interval_f = strtof(optarg, NULL); if (report_interval_f < 0) { fatal(14, "-r: invalid interval '%s'\n", optarg); } args.report_interval_ms = (int)(report_interval_f * 1000); break; case 'p': set_my_priority = 1; break; case LONG_OPT_PREFER: prefer_cmds = optarg; break; case LONG_OPT_AVOID: avoid_cmds = optarg; break; case LONG_OPT_DRYRUN: warn("dryrun mode enabled, will not kill anything\n"); args.dryrun = 1; break; case 'h': fprintf(stderr, "Usage: %s [OPTION]...\n" "\n" " -m PERCENT[,KILL_PERCENT] set available memory minimum to PERCENT of total\n" " (default 10 %%).\n" " earlyoom sends SIGTERM once below PERCENT, then\n" " SIGKILL once below KILL_PERCENT (default PERCENT/2).\n" " -s PERCENT[,KILL_PERCENT] set free swap minimum to PERCENT of total (default\n" " 10 %%).\n" " Note: both memory and swap must be below minimum for\n" " earlyoom to act.\n" " -M SIZE[,KILL_SIZE] set available memory minimum to SIZE KiB\n" " -S SIZE[,KILL_SIZE] set free swap minimum to SIZE KiB\n" " -i user-space oom killer should ignore positive\n" " oom_score_adj values\n" " -n enable d-bus notifications\n" " -d enable debugging messages\n" " -v print version information and exit\n" " -r INTERVAL memory report interval in seconds (default 1), set\n" " to 0 to disable completely\n" " -p set niceness of earlyoom to -20 and oom_score_adj to\n" " -100\n" " --prefer REGEX prefer to kill processes matching REGEX\n" " --avoid REGEX avoid killing processes matching REGEX\n" " --dryrun dry run (do not kill any processes)\n" " -h, --help this help text\n", argv[0]); exit(0); case '?': fprintf(stderr, "Try 'earlyoom --help' for more information.\n"); exit(13); } } /* while getopt */ if (optind < argc) { fatal(13, "extra argument not understood: '%s'\n", argv[optind]); } // Merge "-M" with "-m" values if (have_M) { double M_term_percent = 100 * mem_term_kib / (double)m.MemTotalKiB; double M_kill_percent = 100 * mem_kill_kib / (double)m.MemTotalKiB; if (have_m) { // Both -m and -M were passed. Use the lower of both values. args.mem_term_percent = min(args.mem_term_percent, M_term_percent); args.mem_kill_percent = min(args.mem_kill_percent, M_kill_percent); } else { // Only -M was passed. args.mem_term_percent = M_term_percent; args.mem_kill_percent = M_kill_percent; } } // Merge "-S" with "-s" values if (have_S) { double S_term_percent = 100 * swap_term_kib / (double)m.SwapTotalKiB; double S_kill_percent = 100 * swap_kill_kib / (double)m.SwapTotalKiB; if (have_s) { // Both -s and -S were passed. Use the lower of both values. args.swap_term_percent = min(args.swap_term_percent, S_term_percent); args.swap_kill_percent = min(args.swap_kill_percent, S_kill_percent); } else { // Only -S was passed. args.swap_term_percent = S_term_percent; args.swap_kill_percent = S_kill_percent; } } if (prefer_cmds) { args.prefer_regex = &_prefer_regex; if (regcomp(args.prefer_regex, prefer_cmds, REG_EXTENDED | REG_NOSUB) != 0) { fatal(6, "could not compile regexp '%s'\n", prefer_cmds); } fprintf(stderr, "Preferring to kill process names that match regex '%s'\n", prefer_cmds); } if (avoid_cmds) { args.avoid_regex = &_avoid_regex; if (regcomp(args.avoid_regex, avoid_cmds, REG_EXTENDED | REG_NOSUB) != 0) { fatal(6, "could not compile regexp '%s'\n", avoid_cmds); } fprintf(stderr, "Will avoid killing process names that match regex '%s'\n", avoid_cmds); } if (set_my_priority) { bool fail = 0; if (setpriority(PRIO_PROCESS, 0, -20) != 0) { warn("Could not set priority: %s. Continuing anyway\n", strerror(errno)); fail = 1; } int ret = set_oom_score_adj(-100); if (ret != 0) { warn("Could not set oom_score_adj: %s. Continuing anyway\n", strerror(ret)); fail = 1; } if (!fail) { fprintf(stderr, "Priority was raised successfully\n"); } } // Print memory limits fprintf(stderr, "mem total: %4lld MiB, swap total: %4lld MiB\n", m.MemTotalMiB, m.SwapTotalMiB); fprintf(stderr, "sending SIGTERM when mem <= " PRIPCT " and swap <= " PRIPCT ",\n", args.mem_term_percent, args.swap_term_percent); fprintf(stderr, " SIGKILL when mem <= " PRIPCT " and swap <= " PRIPCT "\n", args.mem_kill_percent, args.swap_kill_percent); /* Dry-run oom kill to make sure stack grows to maximum size before * calling mlockall() */ debug("dry-running kill_largest_process()...\n"); { procinfo_t victim = find_largest_process(&args); kill_process(&args, 0, victim); } int err = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT); // kernels older than 4.4 don't support MCL_ONFAULT. Retry without it. if (err != 0) { err = mlockall(MCL_CURRENT | MCL_FUTURE); } if (err != 0) { perror("Could not lock memory - continuing anyway"); } // Jump into main poll loop poll_loop(&args); return 0; } // Returns errno (success = 0) static int set_oom_score_adj(int oom_score_adj) { char buf[256]; pid_t pid = getpid(); snprintf(buf, sizeof(buf), "/proc/%d/oom_score_adj", pid); FILE* f = fopen(buf, "w"); if (f == NULL) { return -1; } // fprintf returns a negative error code on failure int ret1 = fprintf(f, "%d", oom_score_adj); // fclose returns a non-zero value on failure and errno contains the error code int ret2 = fclose(f); if (ret1 < 0) { return -ret1; } if (ret2) { return errno; } return 0; } /* Calculate the time we should sleep based upon how far away from the memory and swap * limits we are (headroom). Returns a millisecond value between 100 and 1000 (inclusive). * The idea is simple: if memory and swap can only fill up so fast, we know how long we can sleep * without risking to miss a low memory event. */ static unsigned sleep_time_ms(const poll_loop_args_t* args, const meminfo_t* m) { // Maximum expected memory/swap fill rate. In kiB per millisecond ==~ MiB per second. const long long mem_fill_rate = 6000; // 6000MiB/s seen with "stress -m 4 --vm-bytes 4G" const long long swap_fill_rate = 800; // 800MiB/s seen with membomb on ZRAM // Clamp calculated value to this range (milliseconds) const unsigned min_sleep = 100; const unsigned max_sleep = 1000; long long mem_headroom_kib = (long long)((m->MemAvailablePercent - args->mem_term_percent) * 10 * (double)m->MemTotalMiB); if (mem_headroom_kib < 0) { mem_headroom_kib = 0; } long long swap_headroom_kib = (long long)((m->SwapFreePercent - args->swap_term_percent) * 10 * (double)m->SwapTotalMiB); if (swap_headroom_kib < 0) { swap_headroom_kib = 0; } long long ms = mem_headroom_kib / mem_fill_rate + swap_headroom_kib / swap_fill_rate; if (ms < min_sleep) { return min_sleep; } if (ms > max_sleep) { return max_sleep; } return (unsigned)ms; } /* lowmem_sig compares the limits with the current memory situation * and returns which signal (SIGKILL, SIGTERM, 0) should be sent in * response. 0 means that there is enough memory and we should * not kill anything. */ static int lowmem_sig(const poll_loop_args_t* args, const meminfo_t* m) { if (m->MemAvailablePercent <= args->mem_kill_percent && m->SwapFreePercent <= args->swap_kill_percent) return SIGKILL; else if (m->MemAvailablePercent <= args->mem_term_percent && m->SwapFreePercent <= args->swap_term_percent) return SIGTERM; return 0; } // poll_loop is the main event loop. Never returns. static void poll_loop(const poll_loop_args_t* args) { // Print a a memory report when this reaches zero. We start at zero so // we print the first report immediately. int report_countdown_ms = 0; while (1) { meminfo_t m = parse_meminfo(); int sig = lowmem_sig(args, &m); if (sig == SIGKILL) { print_mem_stats(warn, m); warn("low memory! at or below SIGKILL limits: mem " PRIPCT ", swap " PRIPCT "\n", args->mem_kill_percent, args->swap_kill_percent); } else if (sig == SIGTERM) { print_mem_stats(warn, m); warn("low memory! at or below SIGTERM limits: mem " PRIPCT ", swap " PRIPCT "\n", args->mem_term_percent, args->swap_term_percent); } if (sig) { procinfo_t victim = find_largest_process(args); /* The run time of find_largest_process is proportional to the number * of processes, and takes 2.5ms on my box with a running Gnome desktop (try "make bench"). * This is long enough that the situation may have changed in the meantime, * so we double-check if we still need to kill anything. * The run time of parse_meminfo is only 6us on my box and independent of the number * of processes (try "make bench"). */ m = parse_meminfo(); if(lowmem_sig(args, &m) == 0) { warn("memory situation has recovered while selecting victim\n"); } else { kill_process(args, sig, victim); } } else if (args->report_interval_ms && report_countdown_ms <= 0) { print_mem_stats(printf, m); report_countdown_ms = args->report_interval_ms; } unsigned sleep_ms = sleep_time_ms(args, &m); debug("adaptive sleep time: %d ms\n", sleep_ms); struct timespec req = { .tv_sec = (time_t)(sleep_ms / 1000), .tv_nsec = (sleep_ms % 1000) * 1000000 }; nanosleep(&req, NULL); report_countdown_ms -= (int)sleep_ms; } } earlyoom-1.6.2/meminfo.c000066400000000000000000000200161374163754100151570ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* Parse /proc/meminfo * Returned values are in kiB */ #include #include // for size_t #include #include #include #include #include #include "globals.h" #include "meminfo.h" #include "msg.h" /* Parse the contents of /proc/meminfo (in buf), return value of "name" * (example: "MemTotal:") * Returns -errno if the entry cannot be found. */ static long long get_entry(const char* name, const char* buf) { char* hit = strstr(buf, name); if (hit == NULL) { return -ENODATA; } errno = 0; long long val = strtoll(hit + strlen(name), NULL, 10); if (errno != 0) { int strtoll_errno = errno; warn("%s: strtol() failed: %s", __func__, strerror(errno)); return -strtoll_errno; } return val; } /* Like get_entry(), but exit if the value cannot be found */ static long long get_entry_fatal(const char* name, const char* buf) { long long val = get_entry(name, buf); if (val < 0) { warn("%s: fatal error, dumping buffer for later diagnosis:\n%s", __func__, buf); fatal(104, "could not find entry '%s' in /proc/meminfo: %s\n", name, strerror((int)-val)); } return val; } /* If the kernel does not provide MemAvailable (introduced in Linux 3.14), * approximate it using other data we can get */ static long long available_guesstimate(const char* buf) { long long Cached = get_entry_fatal("Cached:", buf); long long MemFree = get_entry_fatal("MemFree:", buf); long long Buffers = get_entry_fatal("Buffers:", buf); long long Shmem = get_entry_fatal("Shmem:", buf); return MemFree + Cached + Buffers - Shmem; } /* Parse /proc/meminfo. * This function either returns valid data or kills the process * with a fatal error. */ meminfo_t parse_meminfo() { // Note that we do not need to close static FDs that we ensure to // `fopen()` maximally once. static FILE* fd; static int guesstimate_warned = 0; // On Linux 5.3, "wc -c /proc/meminfo" counts 1391 bytes. // 8192 should be enough for the foreseeable future. char buf[8192] = { 0 }; meminfo_t m = { 0 }; if (fd == NULL) fd = fopen("/proc/meminfo", "r"); if (fd == NULL) { fatal(102, "could not open /proc/meminfo: %s\n", strerror(errno)); } rewind(fd); size_t len = fread(buf, 1, sizeof(buf) - 1, fd); if (ferror(fd)) { fatal(103, "could not read /proc/meminfo: %s\n", strerror(errno)); } if (len == 0) { fatal(103, "could not read /proc/meminfo: 0 bytes returned\n"); } m.MemTotalKiB = get_entry_fatal("MemTotal:", buf); m.SwapTotalKiB = get_entry_fatal("SwapTotal:", buf); long long SwapFree = get_entry_fatal("SwapFree:", buf); long long MemAvailable = get_entry("MemAvailable:", buf); if (MemAvailable < 0) { MemAvailable = available_guesstimate(buf); if (guesstimate_warned == 0) { fprintf(stderr, "Warning: Your kernel does not provide MemAvailable data (needs 3.14+)\n" " Falling back to guesstimate\n"); guesstimate_warned = 1; } } // Calculate percentages m.MemAvailablePercent = (double)MemAvailable * 100 / (double)m.MemTotalKiB; if (m.SwapTotalKiB > 0) { m.SwapFreePercent = (double)SwapFree * 100 / (double)m.SwapTotalKiB; } else { m.SwapFreePercent = 0; } // Convert kiB to MiB m.MemTotalMiB = m.MemTotalKiB / 1024; m.MemAvailableMiB = MemAvailable / 1024; m.SwapTotalMiB = m.SwapTotalKiB / 1024; m.SwapFreeMiB = SwapFree / 1024; return m; } bool is_alive(int pid) { char buf[256]; // Read /proc/[pid]/stat snprintf(buf, sizeof(buf), "/proc/%d/stat", pid); FILE* f = fopen(buf, "r"); if (f == NULL) { // Process is gone - good. return false; } // File content looks like this: // 10751 (cat) R 2663 10751 2663[...] char state; int res = fscanf(f, "%*d %*s %c", &state); int fscanf_errno = errno; fclose(f); if (res < 1) { warn("is_alive: fscanf() failed: %s\n", strerror(fscanf_errno)); return false; } debug("process state: %c\n", state); if (state == 'Z') { // A zombie process does not use any memory. Consider it dead. return false; } return true; } /* Read /proc/[pid]/[name] and convert to integer. * As the value may legitimately be < 0 (think oom_score_adj), * it is stored in the `out` pointer, and the return value is either * 0 (sucess) or -errno (failure). */ static int read_proc_file_integer(const int pid, const char* name, int* out) { char path[PATH_LEN] = { 0 }; snprintf(path, sizeof(path), "/proc/%d/%s", pid, name); FILE* f = fopen(path, "r"); if (f == NULL) { return -errno; } int matches = fscanf(f, "%d", out); fclose(f); if (matches != 1) { return -ENODATA; } return 0; } /* Read /proc/[pid]/oom_score. * Returns the value (>= 0) or -errno on error. */ int get_oom_score(const int pid) { int out = 0; int res = read_proc_file_integer(pid, "oom_score", &out); if (res < 0) { return res; } return out; } /* Read /proc/[pid]/oom_score_adj. * As the value may legitimately be negative, the return value is * only used for error indication, and the value is stored in * the `out` pointer. * Returns 0 on success and -errno on error. */ int get_oom_score_adj(const int pid, int* out) { return read_proc_file_integer(pid, "oom_score_adj", out); } /* Read /proc/[pid]/comm (process name truncated to 16 bytes). * Returns 0 on success and -errno on error. */ int get_comm(int pid, char* out, size_t outlen) { char path[PATH_LEN] = { 0 }; snprintf(path, sizeof(path), "/proc/%d/comm", pid); FILE* f = fopen(path, "r"); if (f == NULL) { return -errno; } size_t n = fread(out, 1, outlen - 1, f); if (ferror(f)) { int fread_errno = errno; perror("get_comm: fread() failed"); fclose(f); return -fread_errno; } fclose(f); // Process name may be empty, but we should get at least a newline // Example for empty process name: perl -MPOSIX -e '$0=""; pause' if (n < 1) { return -ENODATA; } // Strip trailing newline out[n - 1] = 0; fix_truncated_utf8(out); return 0; } // Get the effective uid (EUID) of `pid`. // Returns the uid (>= 0) or -errno on error. int get_uid(int pid) { char path[PATH_LEN] = { 0 }; snprintf(path, sizeof(path), "/proc/%d", pid); struct stat st = { 0 }; int res = stat(path, &st); if (res < 0) { return -errno; } return (int)st.st_uid; } // Read VmRSS from /proc/[pid]/statm and convert to kiB. // Returns the value (>= 0) or -errno on error. long long get_vm_rss_kib(int pid) { long long vm_rss_kib = -1; char path[PATH_LEN] = { 0 }; // Read VmRSS from /proc/[pid]/statm (in pages) snprintf(path, sizeof(path), "/proc/%d/statm", pid); FILE* f = fopen(path, "r"); if (f == NULL) { return -errno; } int matches = fscanf(f, "%*u %lld", &vm_rss_kib); fclose(f); if (matches < 1) { return -ENODATA; } // Read and cache page size static long page_size; if (page_size == 0) { page_size = sysconf(_SC_PAGESIZE); if (page_size <= 0) { fatal(1, "could not read page size\n"); } } // Convert to kiB vm_rss_kib = vm_rss_kib * page_size / 1024; return vm_rss_kib; } /* Print a status line like * mem avail: 5259 MiB (67 %), swap free: 0 MiB (0 %)" * as an informational message to stdout (default), or * as a warning to stderr. */ void print_mem_stats(int __attribute__((format(printf, 1, 2))) (*out_func)(const char* fmt, ...), const meminfo_t m) { out_func("mem avail: %5lld of %5lld MiB (" PRIPCT "), swap free: %4lld of %4lld MiB (" PRIPCT ")\n", m.MemAvailableMiB, m.MemTotalMiB, m.MemAvailablePercent, m.SwapFreeMiB, m.SwapTotalMiB, m.SwapFreePercent); } earlyoom-1.6.2/meminfo.h000066400000000000000000000020121374163754100151600ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef MEMINFO_H #define MEMINFO_H #define PATH_LEN 256 #include typedef struct { // Values from /proc/meminfo, in KiB or converted to MiB. long long MemTotalKiB; long long MemTotalMiB; long long MemAvailableMiB; // -1 means no data available long long SwapTotalMiB; long long SwapTotalKiB; long long SwapFreeMiB; // Calculated percentages double MemAvailablePercent; // percent of total memory that is available double SwapFreePercent; // percent of total swap that is free } meminfo_t; typedef struct procinfo { int pid; int uid; int badness; long long VmRSSkiB; char name[PATH_LEN]; } procinfo_t; meminfo_t parse_meminfo(); bool is_alive(int pid); void print_mem_stats(int (*out_func)(const char* fmt, ...), const meminfo_t m); int get_oom_score(int pid); int get_oom_score_adj(const int pid, int* out); long long get_vm_rss_kib(int pid); int get_comm(int pid, char* out, size_t outlen); int get_uid(int pid); #endif earlyoom-1.6.2/msg.c000066400000000000000000000123611374163754100143170ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include #include #include #include #include // need strlen() #include #include "globals.h" #include "msg.h" // color_log writed to `f`, prefixing the `color` code if `f` is a tty. static void color_log(FILE* f, const char* color, const char* fmt, va_list vl) { char* reset = "\033[0m"; if (!isatty(fileno(f))) { color = ""; reset = ""; } fputs(color, f); vfprintf(f, fmt, vl); fputs(reset, f); // The `reset` control was not flushed out by the // newline as it was sent after. Manually flush // it now to prevent artifacts when stderr and stdout // mix. if (fmt[strlen(fmt) - 1] == '\n') { fflush(f); } } // Print message, prefixed with "fatal: ", to stderr and exit with "code". // Example: fatal(6, "could not compile regexp '%s'\n", regex_str); int fatal(int code, char* fmt, ...) { const char* red = "\033[31m"; char fmt2[MSG_LEN] = { 0 }; snprintf(fmt2, sizeof(fmt2), "fatal: %s", fmt); va_list vl; va_start(vl, fmt); color_log(stderr, red, fmt2, vl); va_end(vl); exit(code); } // Print a yellow warning message to stderr. No "warning" prefix is added. int warn(const char* fmt, ...) { const char* yellow = "\033[33m"; va_list vl; va_start(vl, fmt); color_log(stderr, yellow, fmt, vl); va_end(vl); return 0; } // Print a gray debug message to stdout. No prefix is added. int debug(const char* fmt, ...) { if (!enable_debug) { return 0; } const char* gray = "\033[2m"; va_list vl; va_start(vl, fmt); color_log(stdout, gray, fmt, vl); va_end(vl); return 0; } // Parse a floating point value, check conversion errors and allowed range. // Guaranteed value range: 0 <= val <= upper_limit. // An error is indicated by storing an error message in tuple->err and returning 0. static double parse_part(term_kill_tuple_t* tuple, const char* part, long long upper_limit) { errno = 0; char* endptr = 0; double val = strtod(part, &endptr); if (*endptr != '\0') { snprintf(tuple->err, sizeof(tuple->err), "trailing garbage '%s'", endptr); return 0; } if (errno) { snprintf(tuple->err, sizeof(tuple->err), "converson error: %s", strerror(errno)); return 0; } if (val > (double)upper_limit) { snprintf(tuple->err, sizeof(tuple->err), "value %lf exceeds limit %lld", val, upper_limit); return 0; } if (val < 0) { snprintf(tuple->err, sizeof(tuple->err), "value %lf below zero", val); return 0; } return val; } // Parse the "term[,kill]" tuple in optarg, examples: "123", "123,456". // Guaranteed value range: 0 <= term <= kill <= upper_limit. term_kill_tuple_t parse_term_kill_tuple(const char* optarg, long long upper_limit) { term_kill_tuple_t tuple = { 0 }; // writable copy of optarg char buf[MSG_LEN] = { 0 }; if (strlen(optarg) > (sizeof(buf) - 1)) { snprintf(tuple.err, sizeof(tuple.err), "argument too long (%zu bytes)", strlen(optarg)); return tuple; } strncpy(buf, optarg, sizeof(buf) - 1); // Split string on "," into two parts char* part1 = buf; char* part2 = NULL; char* comma = strchr(buf, ','); if (comma) { // Zero-out the comma, truncates part1 *comma = '\0'; // part2 gets zero or more bytes after the comma part2 = comma + 1; } // Parse part1 tuple.term = parse_part(&tuple, part1, upper_limit); if (strlen(tuple.err)) { return tuple; } if (part2) { // Parse part2 tuple.kill = parse_part(&tuple, part2, upper_limit); if (strlen(tuple.err)) { return tuple; } } else { // User passed only the SIGTERM value: the SIGKILL value is calculated as // SIGTERM/2. tuple.kill = tuple.term / 2; } // Setting term < kill makes no sense if (tuple.term < tuple.kill) { warn("warning: SIGTERM value %.2lf is below SIGKILL value %.2lf, setting SIGTERM = SIGKILL = %.2lf\n", tuple.term, tuple.kill, tuple.kill); tuple.term = tuple.kill; } // Sanity checks if (tuple.kill == 0 && tuple.term == 0) { snprintf(tuple.err, sizeof(tuple.err), "both SIGTERM and SIGKILL values are zero"); return tuple; } return tuple; } // Credit to https://gist.github.com/w-vi/67fe49106c62421992a2 // Only works for string of length 3 and up. This is good enough // for our use case, which is fixing the 16-byte value we get // from /proc/[pid]/comm. // // Tested in unit_test.go: Test_fix_truncated_utf8() void fix_truncated_utf8(char* str) { size_t len = strlen(str); if (len < 3) { return; } // We only need to look at the last three bytes char* b = str + len - 3; // Last byte is ascii if ((b[2] & 0x80) == 0) { return; } // Last byte is multi-byte sequence start if (b[2] & 0x40) { b[2] = 0; } // Truncated 3-byte sequence else if ((b[1] & 0xe0) == 0xe0) { b[1] = 0; // Truncated 4-byte sequence } else if ((b[0] & 0xf0) == 0xf0) { b[0] = 0; } } earlyoom-1.6.2/msg.h000066400000000000000000000016371374163754100143300ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef MSG_H #define MSG_H #include #define MSG_LEN 256 // printf format for percentages #define PRIPCT "%5.2lf%%" /* From https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Function-Attributes.html : * The format attribute specifies that a function takes printf * style arguments that should be type-checked against a format string. */ int fatal(int code, char* fmt, ...) __attribute__((noreturn, format(printf, 2, 3))); int warn(const char* fmt, ...) __attribute__((format(printf, 1, 2))); int debug(const char* fmt, ...) __attribute__((format(printf, 1, 2))); typedef struct { // If the conversion failed, err contains the error message. char err[255]; // Parsed values. double term; double kill; } term_kill_tuple_t; term_kill_tuple_t parse_term_kill_tuple(const char* optarg, long long upper_limit); void fix_truncated_utf8(char* str); #endif earlyoom-1.6.2/testsuite_c_wrappers.go000066400000000000000000000026701374163754100201740ustar00rootroot00000000000000package earlyoom_testsuite import ( "fmt" "strings" ) // #cgo CFLAGS: -std=gnu99 -DCGO // #include "meminfo.h" // #include "kill.h" // #include "msg.h" import "C" func parse_term_kill_tuple(optarg string, upper_limit int) (error, float64, float64) { cs := C.CString(optarg) tuple := C.parse_term_kill_tuple(cs, C.longlong(upper_limit)) errmsg := C.GoString(&(tuple.err[0])) if len(errmsg) > 0 { return fmt.Errorf(errmsg), 0, 0 } return nil, float64(tuple.term), float64(tuple.kill) } func is_alive(pid int) bool { res := C.is_alive(C.int(pid)) return bool(res) } func fix_truncated_utf8(str string) string { cstr := C.CString(str) C.fix_truncated_utf8(cstr) return C.GoString(cstr) } func parse_meminfo() C.meminfo_t { return C.parse_meminfo() } func find_largest_process() { var args C.poll_loop_args_t C.find_largest_process(&args) } func kill_process() { var args C.poll_loop_args_t var victim C.procinfo_t victim.pid = 1 C.kill_process(&args, 0, victim) } func get_oom_score(pid int) int { return int(C.get_oom_score(C.int(pid))) } func get_oom_score_adj(pid int, out *int) int { var out2 C.int res := C.get_oom_score_adj(C.int(pid), &out2) *out = int(out2) return int(res) } func get_vm_rss_kib(pid int) int { return int(C.get_vm_rss_kib(C.int(pid))) } func get_comm(pid int) (int, string) { cstr := C.CString(strings.Repeat("\000", 256)) res := C.get_comm(C.int(pid), cstr, 256) return int(res), C.GoString(cstr) } earlyoom-1.6.2/testsuite_cli_test.go000066400000000000000000000237621374163754100176420ustar00rootroot00000000000000package earlyoom_testsuite import ( "fmt" "io/ioutil" "math" "os/exec" "regexp" "strconv" "strings" "testing" ) type cliTestCase struct { // arguments to pass to earlyoom args []string // expected exit code code int // stdout must contain stdoutContains string // stdout must be empty? stdoutEmpty bool // stderr must contain stderrContains string // stderr must be empty? stderrEmpty bool } func paseMeminfoLine(l string) int64 { fields := strings.Split(l, " ") asciiVal := fields[len(fields)-2] val, err := strconv.ParseInt(asciiVal, 10, 64) if err != nil { panic(err) } return val } func parseMeminfo() (memTotal int64, swapTotal int64) { /* /proc/meminfo looks like this: MemTotal: 8024108 kB [...] SwapTotal: 102396 kB [...] */ content, err := ioutil.ReadFile("/proc/meminfo") if err != nil { panic(err) } lines := strings.Split(string(content), "\n") for _, l := range lines { if strings.HasPrefix(l, "MemTotal:") { memTotal = paseMeminfoLine(l) } if strings.HasPrefix(l, "SwapTotal:") { swapTotal = paseMeminfoLine(l) } } return } // earlyoom RSS should never be above 1 MiB const rssMax = 1024 func TestCli(t *testing.T) { memTotal, swapTotal := parseMeminfo() mem1percent := fmt.Sprintf("%d", memTotal*1/100) swap2percent := fmt.Sprintf("%d", swapTotal*2/100) tooBigInt32 := fmt.Sprintf("%d", uint64(math.MaxInt32+1)) tooBigUint32 := fmt.Sprintf("%d", uint64(math.MaxUint32+1)) // earlyoom startup looks like this: // earlyoom v1.1-5-g74a364b-dirty // mem total: 7836 MiB, min: 783 MiB (10 %) // swap total: 0 MiB, min: 0 MiB (10 %) // startupMsg matches the last line of the startup output. const startupMsg = "swap total: " testcases := []cliTestCase{ // Both -h and --help should show the help text {args: []string{"-h"}, code: 0, stderrContains: "this help text", stdoutEmpty: true}, {args: []string{"--help"}, code: 0, stderrContains: "this help text", stdoutEmpty: true}, {args: nil, code: -1, stderrContains: startupMsg, stdoutContains: memReport}, {args: []string{"-p"}, code: -1, stdoutContains: memReport}, {args: []string{"-v"}, code: 0, stderrContains: "earlyoom v", stdoutEmpty: true}, {args: []string{"-d"}, code: -1, stdoutContains: "new victim"}, {args: []string{"-m", "1"}, code: -1, stderrContains: " 1.00%", stdoutContains: memReport}, {args: []string{"-m", "0"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-m", "-10"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, // Using "-m 100" makes no sense {args: []string{"-m", "100"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", "2"}, code: -1, stderrContains: " 2.00%", stdoutContains: memReport}, // Using "-s 100" is a valid way to ignore swap usage {args: []string{"-s", "100"}, code: -1, stderrContains: " 100.00%", stdoutContains: memReport}, {args: []string{"-s", "101"}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", "0"}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", "-10"}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-M", mem1percent}, code: -1, stderrContains: " 1.00%", stdoutContains: memReport}, {args: []string{"-M", "9999999999999999"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, // We use {"-r=0"} instead of {"-r", "0"} so runEarlyoom() can detect that there will be no output {args: []string{"-r=0"}, code: -1, stderrContains: startupMsg, stdoutEmpty: true}, {args: []string{"-r", "0.1"}, code: -1, stderrContains: startupMsg, stdoutContains: memReport}, // Test --avoid and --prefer {args: []string{"--avoid", "MyProcess1"}, code: -1, stderrContains: "Will avoid killing", stdoutContains: memReport}, {args: []string{"--prefer", "MyProcess2"}, code: -1, stderrContains: "Preferring to kill", stdoutContains: memReport}, {args: []string{"-i"}, code: -1, stderrContains: "Ignoring positive oom_score_adj values"}, // Extra arguments should error out {args: []string{"xyz"}, code: 13, stderrContains: "extra argument not understood", stdoutEmpty: true}, {args: []string{"-i", "1"}, code: 13, stderrContains: "extra argument not understood", stdoutEmpty: true}, // Tuples {args: []string{"-m", "2,1"}, code: -1, stderrContains: "sending SIGTERM when mem <= 2.00% and swap <= 10.00%", stdoutContains: memReport}, {args: []string{"-m", "1,2"}, code: -1, stdoutContains: memReport}, {args: []string{"-m", "1,-1"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-m", "1000,-1000"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", "2,1"}, code: -1, stderrContains: "sending SIGTERM when mem <= 10.00% and swap <= 2.00%", stdoutContains: memReport}, {args: []string{"-s", "1,2"}, code: -1, stdoutContains: memReport}, // https://github.com/rfjakob/earlyoom/issues/97 {args: []string{"-m", "5,0"}, code: -1, stdoutContains: memReport}, {args: []string{"-m", "5,9"}, code: -1, stdoutContains: memReport}, // check for integer overflows {args: []string{"-M", "-1"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-M", tooBigInt32}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-M", tooBigUint32}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-m", "-1"}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-m", tooBigInt32}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-m", tooBigUint32}, code: 15, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-S", "-1"}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-S", tooBigInt32}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-S", tooBigUint32}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", "-1"}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", tooBigInt32}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, {args: []string{"-s", tooBigUint32}, code: 16, stderrContains: "fatal", stdoutEmpty: true}, // Floating point values {args: []string{"-m", "3.14"}, code: -1, stderrContains: "SIGTERM when mem <= 3.14%", stdoutContains: memReport}, {args: []string{"-m", "7,3.14"}, code: -1, stderrContains: "SIGKILL when mem <= 3.14%", stdoutContains: memReport}, {args: []string{"-s", "12.34"}, code: -1, stderrContains: "swap <= 12.34%", stdoutContains: memReport}, // Use both -m/-M {args: []string{"-m", "10", "-M", mem1percent}, code: -1, stderrContains: "SIGTERM when mem <= 1.00%", stdoutContains: memReport}, } if swapTotal > 0 { // Tests that cannot work when there is no swap enabled tc := []cliTestCase{ {args: []string{"-S", swap2percent}, code: -1, stderrContains: " 2.00%", stdoutContains: memReport}, // Use both -s/-S {args: []string{"-s", "10", "-S", swap2percent}, code: -1, stderrContains: "swap <= 1.00%", stdoutContains: memReport}, } testcases = append(testcases, tc...) } for _, tc := range testcases { name := fmt.Sprintf("%s", strings.Join(tc.args, " ")) t.Run(name, func(t *testing.T) { pass := true res := runEarlyoom(t, tc.args...) if res.code != tc.code { t.Errorf("wrong exit code: have=%d want=%d", res.code, tc.code) pass = false } if tc.stdoutEmpty && res.stdout != "" { t.Errorf("stdout should be empty but is not") pass = false } if !strings.Contains(res.stdout, tc.stdoutContains) { t.Errorf("stdout should contain %q, but does not", tc.stdoutContains) pass = false } if tc.stderrEmpty && res.stderr != "" { t.Errorf("stderr should be empty, but is not") pass = false } if !strings.Contains(res.stderr, tc.stderrContains) { t.Errorf("stderr should contain %q, but does not", tc.stderrContains) pass = false } if res.rss > rssMax { t.Errorf("Memory usage too high! actual rss: %d, rssMax: %d", res.rss, rssMax) pass = false } /* $ ls -l /proc/42277/fd total 0 lrwx------. 1 jakob jakob 64 Feb 22 14:36 0 -> /dev/pts/2 lrwx------. 1 jakob jakob 64 Feb 22 14:36 1 -> /dev/pts/2 lrwx------. 1 jakob jakob 64 Feb 22 14:36 2 -> /dev/pts/2 lr-x------. 1 jakob jakob 64 Feb 22 14:36 3 -> /proc/meminfo Plus one for /proc/[pid]/stat which may possibly be open as well */ if res.fds > 5 { t.Fatalf("High number of open file descriptors: %d", res.fds) } if !pass { const empty = "(empty)" if res.stderr == "" { res.stderr = empty } if res.stdout == "" { res.stdout = empty } t.Logf("stderr:\n%s", res.stderr) t.Logf("stdout:\n%s", res.stdout) } }) } } func TestRss(t *testing.T) { res := runEarlyoom(t) if res.rss == 0 { t.Error("rss is zero!?") } if res.rss > rssMax { t.Error("rss above 1 MiB") } t.Logf("earlyoom RSS: %d kiB", res.rss) } // TestI tests that `earlyoom -i` works as expected func TestI(t *testing.T) { cmd := exec.Command("sleep", "60") err := cmd.Start() if err != nil { t.Fatal(err) } defer cmd.Process.Kill() path := fmt.Sprintf("/proc/%d/oom_score_adj", cmd.Process.Pid) err = ioutil.WriteFile(path, []byte("1000"), 0600) if err != nil { t.Fatal(err) } res := runEarlyoom(t, "-d") // We should see a line like this: // pid 2308155: badness 1000 vm_rss 708 uid 1026 "sleep" <--- new victim // Or, for some reason, this: // pid 6950: badness 999 vm_rss 772 uid 1026 "sleep" <--- new victim matched := false for _, b := range []int{1000, 999} { pattern := fmt.Sprintf(`pid\s+%d: badness %d`, cmd.Process.Pid, b) matched, err = regexp.MatchString(pattern, res.stdout) if err != nil { t.Fatal(err) } if matched { break } } if !matched { t.Error("did not see badness 1000 or 999 in output") t.Log(res.stdout) } res = runEarlyoom(t, "-d", "-i") pattern := fmt.Sprintf(`pid\s+%d: badness %d`, cmd.Process.Pid, 1000) matched, err = regexp.MatchString(pattern, res.stdout) if err != nil { t.Fatal(err) } if matched { t.Error("saw badness 1000, but should not have") } } earlyoom-1.6.2/testsuite_helpers.go000066400000000000000000000052311374163754100174650ustar00rootroot00000000000000package earlyoom_testsuite import ( "bufio" "bytes" "fmt" "log" "os" "os/exec" "syscall" "testing" "time" ) type exitVals struct { stdout string stderr string // Exit code code int // RSS in kiB rss int // Number of file descriptors fds int } const earlyoomBinary = "./earlyoom" // The periodic memory report looks like this: // mem avail: 4998 MiB (63 %), swap free: 0 MiB (0 %) const memReport = "mem avail: " // runEarlyoom runs earlyoom, waits for the first "mem avail:" status line, // and kills it. func runEarlyoom(t *testing.T, args ...string) exitVals { var stdoutBuf, stderrBuf bytes.Buffer cmd := exec.Command(earlyoomBinary, args...) cmd.Stderr = &stderrBuf p, err := cmd.StdoutPipe() if err != nil { t.Fatal(err) } stdoutScanner := bufio.NewScanner(p) // If "-r=0" is passed, earlyoom will not print a memory report, // so we set a shorter timeout and not report an error on timeout // kill. expectMemReport := true for _, a := range args { if a == "-r=0" { expectMemReport = false } } var timer *time.Timer if expectMemReport { timer = time.AfterFunc(10*time.Second, func() { t.Error("timeout") cmd.Process.Kill() }) } else { timer = time.AfterFunc(100*time.Millisecond, func() { cmd.Process.Kill() }) } err = cmd.Start() if err != nil { t.Fatal(err) } // Read until the first status line, looks like this: // mem avail: 19377 of 23915 MiB (81 %), swap free: 0 of 0 MiB ( 0 %) for stdoutScanner.Scan() { line := stdoutScanner.Bytes() stdoutBuf.Write(line) // Scanner strips the newline, add it back stdoutBuf.Write([]byte{'\n'}) if bytes.HasPrefix(line, []byte(memReport)) { break } } timer.Stop() rss := get_vm_rss_kib(cmd.Process.Pid) fds := countFds(cmd.Process.Pid) cmd.Process.Kill() err = cmd.Wait() return exitVals{ code: extractCmdExitCode(err), stdout: string(stdoutBuf.Bytes()), stderr: string(stderrBuf.Bytes()), rss: rss, fds: fds, } } func countFds(pid int) int { dir := fmt.Sprintf("/proc/%d/fd", pid) f, err := os.Open(dir) if err != nil { return -1 } defer f.Close() // Note: Readdirnames filters "." and ".." names, err := f.Readdirnames(0) if err != nil { return -1 } return len(names) } // extractCmdExitCode extracts the exit code from an error value that was // returned from exec / cmd.Run() func extractCmdExitCode(err error) int { if err == nil { return 0 } // OMG this is convoluted if err2, ok := err.(*exec.ExitError); ok { return err2.Sys().(syscall.WaitStatus).ExitStatus() } if err2, ok := err.(*os.PathError); ok { return int(err2.Err.(syscall.Errno)) } log.Panicf("could not decode error %#v", err) return 0 } earlyoom-1.6.2/testsuite_unit_test.go000066400000000000000000000114231374163754100200410ustar00rootroot00000000000000package earlyoom_testsuite import ( "os" "strings" "syscall" "testing" "unicode/utf8" ) // On Fedora 31 (Linux 5.4), /proc/sys/kernel/pid_max = 4194304. // It's very unlikely that INT32_MAX will be a valid pid anytime soon. const INT32_MAX = 2147483647 const ENOENT = 2 func TestParseTuple(t *testing.T) { tcs := []struct { arg string limit int shouldFail bool term float64 kill float64 }{ {arg: "2,1", limit: 100, term: 2, kill: 1}, {arg: "20,10", limit: 100, term: 20, kill: 10}, {arg: "30", limit: 100, term: 30, kill: 15}, {arg: "30", limit: 20, shouldFail: true}, // https://github.com/rfjakob/earlyoom/issues/97 {arg: "22[,20]", limit: 100, shouldFail: true}, {arg: "220[,160]", limit: 300, shouldFail: true}, {arg: "180[,170]", limit: 300, shouldFail: true}, {arg: "5,0", limit: 100, term: 5, kill: 0}, {arg: "5,9", limit: 100, term: 9, kill: 9}, {arg: "0,5", limit: 100, term: 5, kill: 5}, // TERM value is set to KILL value when it is below TERM {arg: "4,5", limit: 100, term: 5, kill: 5}, {arg: "0", limit: 100, shouldFail: true}, {arg: "0,0", limit: 100, shouldFail: true}, // Floating point values {arg: "4.0,2.0", limit: 100, term: 4, kill: 2}, {arg: "4,0,2,0", limit: 100, shouldFail: true}, {arg: "3.1415,2.7182", limit: 100, term: 3.1415, kill: 2.7182}, {arg: "3.1415", limit: 100, term: 3.1415, kill: 3.1415 / 2}, {arg: "1." + strings.Repeat("123", 100), limit: 100, shouldFail: true}, // Leading garbage {arg: "x1,x2", limit: 100, shouldFail: true}, {arg: "1,x2", limit: 100, shouldFail: true}, // Trailing garbage {arg: "1x,2x", limit: 100, shouldFail: true}, {arg: "1.1.", limit: 100, shouldFail: true}, {arg: "1,2..", limit: 100, shouldFail: true}, } for _, tc := range tcs { err, term, kill := parse_term_kill_tuple(tc.arg, tc.limit) hasFailed := (err != nil) if tc.shouldFail != hasFailed { t.Errorf("case %v: hasFailed=%v", tc, hasFailed) continue } if term != tc.term { t.Errorf("case %v: term=%v", tc, term) } if kill != tc.kill { t.Errorf("case %v: kill=%v", tc, kill) } } } func TestIsAlive(t *testing.T) { tcs := []struct { pid int res bool }{ {os.Getpid(), true}, {1, true}, {999999, false}, {0, false}, } for _, tc := range tcs { if res := is_alive(tc.pid); res != tc.res { t.Errorf("pid %d: expected %v, got %v", tc.pid, tc.res, res) } } } func Test_fix_truncated_utf8(t *testing.T) { // From https://gist.github.com/w-vi/67fe49106c62421992a2 str := "___😀∮ E⋅da = Q, n → ∞, 𐍈∑ f(i) = ∏ g(i)" // a range loop will split at runes - we *want* broken utf8 so use raw // counter. for i := 3; i < len(str); i++ { truncated := str[:i] fixed := fix_truncated_utf8(truncated) if len(fixed) < 3 { t.Fatalf("truncated: %q", fixed) } if !utf8.Valid([]byte(fixed)) { t.Errorf("Invalid utf8: %q", fixed) } } } func Test_get_vm_rss_kib(t *testing.T) { pid := os.Getpid() rss := get_vm_rss_kib(pid) if rss <= 0 { t.Fatalf("our rss can't be <= 0, but we got %d", rss) } // Error case res := get_vm_rss_kib(INT32_MAX) if res != -ENOENT { t.Fail() } } func Test_get_oom_score(t *testing.T) { res := get_oom_score(os.Getpid()) // On systems with a lot of RAM, our process may actually have a score of // zero. At least check that get_oom_score did not return an error. if res < 0 { t.Error(res) } res = get_oom_score(INT32_MAX) if res != -ENOENT { t.Errorf("want %d, but have %d", syscall.ENOENT, res) } } func Test_get_comm(t *testing.T) { pid := os.Getpid() res, comm := get_comm(pid) if res != 0 { t.Fatalf("error %d", res) } if len(comm) == 0 { t.Fatalf("empty process name %q", comm) } t.Logf("process name %q", comm) // Error case res, comm = get_comm(INT32_MAX) if res != -ENOENT { t.Fail() } if comm != "" { t.Fail() } } func Benchmark_parse_meminfo(b *testing.B) { for n := 0; n < b.N; n++ { parse_meminfo() } } func Benchmark_kill_process(b *testing.B) { for n := 0; n < b.N; n++ { kill_process() } } func Benchmark_find_largest_process(b *testing.B) { for n := 0; n < b.N; n++ { find_largest_process() } } func Benchmark_get_oom_score(b *testing.B) { pid := os.Getpid() for n := 0; n < b.N; n++ { get_oom_score(pid) } } func Benchmark_get_oom_score_adj(b *testing.B) { pid := os.Getpid() for n := 0; n < b.N; n++ { var out int get_oom_score_adj(pid, &out) } } func Benchmark_get_vm_rss_kib(b *testing.B) { pid := os.Getpid() for n := 0; n < b.N; n++ { rss := get_vm_rss_kib(pid) if rss <= 0 { b.Fatalf("rss <= 0: %d", rss) } } } func Benchmark_get_comm(b *testing.B) { pid := os.Getpid() for n := 0; n < b.N; n++ { res, comm := get_comm(pid) if len(comm) == 0 { b.Fatalf("empty process name %q", comm) } if res != 0 { b.Fatalf("error %d", res) } } }