pax_global_header00006660000000000000000000000064146165004400014512gustar00rootroot0000000000000052 comment=d5791d300aafa77b8f6a3d3d97f481a4ef904a38 earlyoom-1.8.2/000077500000000000000000000000001461650044000133515ustar00rootroot00000000000000earlyoom-1.8.2/.clang-format000066400000000000000000000000251461650044000157210ustar00rootroot00000000000000BasedOnStyle: WebKit earlyoom-1.8.2/.github/000077500000000000000000000000001461650044000147115ustar00rootroot00000000000000earlyoom-1.8.2/.github/workflows/000077500000000000000000000000001461650044000167465ustar00rootroot00000000000000earlyoom-1.8.2/.github/workflows/ci.yml000066400000000000000000000042351461650044000200700ustar00rootroot00000000000000name: CI on: push: pull_request: schedule: - cron: '0 23 * * 5' # Every Friday at 23:00 jobs: build: runs-on: ubuntu-latest steps: # Looks like Github Actions leaks fds to child processes # https://github.com/actions/runner/issues/1188 - run: ls -l /proc/self/fd # Build and test in Ubuntu VM provided by Github CI - uses: actions/checkout@v2 with: fetch-depth: 0 # Make "git describe" work - run: sudo apt-get install -qq pandoc cppcheck - run: make - run: make test - run: git clean -dxff # And also build in a number of docker containers of older # distributions - name: Build and test in Debian Bullseye uses: addnab/docker-run-action@v3 with: image: debian:bullseye options: -v ${{ github.workspace }}:/work run: | set -ex apt-get -qq update > /dev/null < /dev/null apt-get -qq install make gcc golang git ca-certificates > /dev/null < /dev/null cd /work make make test - run: git clean -dxff - name: Build and test in Amazon Linux 2 uses: addnab/docker-run-action@v3 with: image: amazonlinux:2 options: -v ${{ github.workspace }}:/work run: | set -ex yum -y -q install make gcc golang git cd /work git config --global --add safe.directory /work # Fix for: fatal: detected dubious ownership in repository at '/work' make make test - run: git clean -dxff - name: Build in Oracle Linux 7 (similar to RHEL 7, CentOS 7) uses: addnab/docker-run-action@v3 with: image: oraclelinux:7 options: -v ${{ github.workspace }}:/work run: | set -ex yum -y -q install make gcc git cd /work make - run: git clean -dxff - name: Build in Oracle Linux 8 (similar to RHEL 8, CentOS 8) uses: addnab/docker-run-action@v3 with: image: oraclelinux:8 options: -v ${{ github.workspace }}:/work run: | set -ex dnf -y -q install make gcc git cd /work make - run: git clean -dxff earlyoom-1.8.2/.github/workflows/codeql.yml000066400000000000000000000053321461650044000207430ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ "master" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] schedule: - cron: '28 3 * * 3' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'cpp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{matrix.language}}" earlyoom-1.8.2/.gitignore000066400000000000000000000002351461650044000153410ustar00rootroot00000000000000*~ /earlyoom /earlyoom.profile /.* # generated from MANPAGE.md /earlyoom.1 /earlyoom.1.gz # generated service files /earlyoom.service /earlyoom.initscript earlyoom-1.8.2/CODE_OF_CONDUCT.md000066400000000000000000000064261461650044000161600ustar00rootroot00000000000000# 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.8.2/Dockerfile000066400000000000000000000002351461650044000153430ustar00rootroot00000000000000FROM gcc as build WORKDIR /usr/src COPY . . ENV CFLAGS -static RUN make ### FROM scratch COPY --from=build /usr/src/earlyoom / ENTRYPOINT ["/earlyoom"] earlyoom-1.8.2/LICENSE000066400000000000000000000020771461650044000143640ustar00rootroot00000000000000The 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.8.2/MANPAGE-render.sh000077500000000000000000000000561461650044000162360ustar00rootroot00000000000000#!/bin/bash make earlyoom.1 man ./earlyoom.1 earlyoom-1.8.2/MANPAGE.md000066400000000000000000000223211461650044000147430ustar00rootroot00000000000000% 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 (if any) are below 10%, it will kill the largest process (highest `oom_score`). The percentage values are 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 `user mem total` (default 10 %). `user mem total`, introduced in earlyoom v1.8, is the memory accessible by userspace (`MemAvailable`+`AnonPages` as reported in `/proc/meminfo`). When a tmpfs ramdisk fills up, `user mem total` shrinks accordingly. By using a percentage of `user mem total` as opposed to total memory, the set memory minimum can always be achieved by killing processes, even when tmpfs fills a large portion of memory. earlyoom sends SIGTERM once **both** available memory **and** free swap are below their respective PERCENT settings. 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 the percentage of `mem total` as reported on startup. `user mem total` is NOT used for the startup calculation because that would make the outcome dependent on how filled tmpfs is at that moment. 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 removed in earlyoom v1.7, ignored for compatibility #### -d, --debug 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. 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. #### -N /PATH/TO/SCRIPT Run the given script for each process killed. Must be an absolute path. Within the script, information about the killed process can be obtained via the following environment variables: EARLYOOM_PID Process PID EARLYOOM_NAME Process name truncated to 16 bytes (as reported in /proc/PID/comm) EARLYOOM_CMDLINE Process cmdline truncated to 256 bytes (as reported in /proc/PID/cmdline) EARLYOOM_UID UID of the user running the process WARNING: `EARLYOOM_NAME` can contain spaces, newlines, special characters and is controlled by the user, or it can be empty! Make sure that your notification script can handle that! #### -g Kill all processes that have same process group id (PGID) as the process with excessive memory usage. For example, with this flag turned on, the whole application will be killed when one of its subprocess consumes too much memory (as long as they all have the same PGID, of course). Enable this flag when completely cleaning up the "entire application" is more desirable, and you are sure that the application puts all its processes in the same PGID. Note that some desktop environments (GNOME, for example) put all desktop application in the same process group as `gnome-shell`. earlyoom might kill all such processes including `gnome-shell` when this flag is turned on. Be sure to check how your environment behaves beforehand. Use pstree -gT to show all processes with the PGID in brackets. #### \-\-prefer REGEX Prefer killing processes whose `comm` name matches REGEX (adds 300 to oom_score). The `comm` name is the string in `/proc/pid/comm`. It is the first 15 bytes of the process name. Longer names are truncated to 15 bytes. The `comm` name is also what `top`, `pstree`, `ps -e` show. Use any of these tools to find the proper `comm` name. Example: You want to match `gnome-control-center`, which is longer than 15 bytes: earlyoom --prefer '^gnome-control-c$' #### \-\-avoid REGEX avoid killing processes whose `comm` name matches REGEX (subtracts 300 from oom_score). #### \-\-ignore REGEX ignore processes whose `comm` name matches REGEX. Unlike the \-\-avoid option, this option disables any potential killing of the matched processes that might have occurred due to the processes attaining a high oom_score. Use this option with caution as other processes might be sacrificed in place of the ignored processes when earlyoom determines to kill processes. ### \-\-sort-by-rss find process with the largest rss (default oom_score) #### \-\-dryrun dry run (do not kill any processes) #### \-\-syslog use syslog instead of std streams. Usually this is not needed as systemd handles logging of all output. The \-\-syslog option may be useful for minimal embedded systems that don't run systemd. See https://github.com/rfjakob/earlyoom/pull/292 for some background info. #### -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). # SEE ALSO nohang(8) earlyoom-1.8.2/Makefile000066400000000000000000000055011461650044000150120ustar00rootroot00000000000000# Setting GIT_DIR keeps git from ascending to parent directories # and gives a nicer error message VERSION ?= $(shell GIT_DIR=$(shell pwd)/.git git describe --tags --dirty) ifeq ($(VERSION),) VERSION := "(unknown version)" $(warning Could not get version from git, setting to $(VERSION)) endif 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) .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) .PHONY: earlyoom.profile earlyoom.profile: $(CC) $(LDFLAGS) $(CPPFLAGS) $(CFLAGS) -DPROFILE_FIND_LARGEST_PROCESS -o earlyoom.profile $(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.profile earlyoom.service earlyoom.initscript earlyoom.1 earlyoom.1.gz gmon.out* 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=file -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.8.2/README.md000066400000000000000000000461451461650044000146420ustar00rootroot00000000000000earlyoom - The Early OOM Daemon =============================== [![CI](https://github.com/rfjakob/earlyoom/actions/workflows/ci.yml/badge.svg)](https://github.com/rfjakob/earlyoom/actions/workflows/ci.yml) [![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) 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. earlyoom wants to be simple and solid. It is written in pure C with no dependencies. An extensive test suite (unit- and integration tests) is written in Go. 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 memory available to userspace processes (=total-shared), it will send the `SIGTERM` signal to the process that uses the most memory in the opinion of the kernel (`/proc/*/oom_score`). #### 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: In some kernel versions (tested on v4.0.5), triggering the kernel oom killer manually does 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). This problem has been fixed in Linux v5.17 ([commit f530243a](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f530243a172d2ff03f88d0056f838928d6445c6d)) . Like the Linux kernel would, earlyoom finds its victim by reading through `/proc/*/oom_score`. 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://src.fedoraproject.org/rpms/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 eearlyoom v1.8 mem total: 23890 MiB, user mem total: 21701 MiB, swap total: 8191 MiB sending SIGTERM when mem avail <= 10.00% and swap free <= 10.00%, SIGKILL when mem avail <= 5.00% and swap free <= 5.00% mem avail: 20012 of 21701 MiB (92.22%), swap free: 5251 of 8191 MiB (64.11%) mem avail: 20031 of 21721 MiB (92.22%), swap free: 5251 of 8191 MiB (64.11%) mem avail: 20033 of 21723 MiB (92.22%), swap free: 5251 of 8191 MiB (64.11%) [...] ``` 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 ``` ### Testing In order to see `earlyoom` in action, create/simulate a memory leak and let `earlyoom` do what it does: ``` tail /dev/zero ``` ### Checking Logs If you need any further actions after a process is killed by `earlyoom` (such as sending emails), you can parse the logs by: ``` sudo journalctl -u earlyoom | grep sending ``` Example output for above test command (`tail /dev/zero`) will look like: ``` Feb 20 10:59:34 debian earlyoom[10231]: sending SIGTERM to process 7378 uid 1000 "tail": oom_score 156, VmRSS 4962 MiB ``` > For older versions of `earlyoom`, use: > > sudo journalctl -u earlyoom | grep -iE "(sending|killing)" > ### 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. Additionally, earlyoom can execute a script for each process killed, providing information about the process via the `EARLYOOM_PID`, `EARLYOOM_UID` and `EARLYOOM_NAME` environment variables. Pass `-N /path/to/script` to enable. Warning: In case of dryrun mode, the script will be executed in rapid succession, ensure you have some sort of rate-limit implemented. ### Preferred Processes The command-line flag `--prefer` specifies processes to prefer killing; likewise, `--avoid` specifies processes to avoid killing. See https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#--prefer-regex for details. 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 v1.8 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 -n enable d-bus notifications -N /PATH/TO/SCRIPT call script after oom kill -g kill all processes within a process group -d, --debug 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 --ignore-root-user do not kill processes owned by root --sort-by-rss find process with the largest rss (default oom_score) --prefer REGEX prefer to kill processes matching REGEX --avoid REGEX avoid killing processes matching REGEX --ignore REGEX ignore processes matching REGEX --dryrun dry run (do not kill any processes) --syslog use syslog instead of std streams -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 Implementation Notes -------------------- * We don't use [procps/libproc2](https://man7.org/linux/man-pages/man3/procps_pids.3.html) because procps_pids_select(), for some reason, always parses /proc/$pid/status. This is relatively expensive, and we don't need it. Changelog --------- * v1.8.2, 2024-05-07 * Fixes in `earlyoom.service` systemd unit file * Add `process_mrelease` to allowed syscalls ([commit](https://github.com/rfjakob/earlyoom/commit/c171b72ba217e923551bdde7e7f00ec5a0488b54)) * Fix `IPAddressDeny` syntax ([commit](e6c7978813413f3ee4181b8c8b11ae088d6e92a4)) * Allow `-p` ([commit](b41ebb2275e59781a8d55a764863417e1e0da5f1)) * v1.8.1, 2024-04-17 * Fix trivial test failures caused by message rewording ([commit](https://github.com/rfjakob/earlyoom/commit/bfde82c001c6e5ec11dfd6e5d13dcee9a9f01229)) * v1.8, 2024-04-15 * Introduce `user mem total` / `meminfo_t.UserMemTotal` and calculate MemAvailablePercent based on it ([commit](https://github.com/rfjakob/earlyoom/commit/459d76296d3d0a0b59ee1e2e48ad2271429de916), [more info in man page](https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#-m-percentkill_percent)) * Use `process_mrelease` ([#266](https://github.com/rfjakob/earlyoom/issues/266)) * Support `NO_COLOR` (https://no-color.org/) * Don't get confused by processes with a zombie main thread ([commit](https://github.com/rfjakob/earlyoom/commit/e54650f0baf7cef7fb1fed3b02cb8e689c6544ea)) * Add `--sort-by-rss`, thanks @RanHuang! This will select a process to kill acc. to the largest RSS instead of largest oom_score. * The Gitlab CI testsuite now also runs on Amazon Linux 2 and Oracle Linux 7. * v1.7, 2022-03-05 * Add `-N` flag to run a script every time a process is killed ([commit](https://github.com/rfjakob/earlyoom/commit/afe03606f077a1a17e6fbc238400b3ce7a9ef2be), [man page section](https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#-n-pathtoscript)) * Add `-g` flag to kill whole process group ([#247](https://github.com/rfjakob/earlyoom/pull/247)) * Remove `-i` flag (ignored for compatibility), it does not work properly on Linux kernels 5.9+ ([#234](https://github.com/rfjakob/earlyoom/issues/234)) * Hardening: Drop ambient capabilities on startup ([#234](https://github.com/rfjakob/earlyoom/pull/228)) * 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 behavior 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.8.2/contrib/000077500000000000000000000000001461650044000150115ustar00rootroot00000000000000earlyoom-1.8.2/contrib/.gitignore000066400000000000000000000000071461650044000167760ustar00rootroot00000000000000zombie earlyoom-1.8.2/contrib/Makefile000066400000000000000000000001761461650044000164550ustar00rootroot00000000000000zombie: *.c gcc -Wall -Wextra -o zombie zombie.c .PHONY: format format: autopep8 -i *.py clang-format --style=file -i *.c earlyoom-1.8.2/contrib/fillmem.py000077500000000000000000000007071461650044000170170ustar00rootroot00000000000000#!/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.8.2/contrib/libproc2-test/000077500000000000000000000000001461650044000175025ustar00rootroot00000000000000earlyoom-1.8.2/contrib/libproc2-test/.gitignore000066400000000000000000000000051461650044000214650ustar00rootroot00000000000000/out earlyoom-1.8.2/contrib/libproc2-test/Makefile000066400000000000000000000001451461650044000211420ustar00rootroot00000000000000out: Makefile *.c gcc *.c -Wall -Wextra -g -o out -lproc2 format: clang-format --style=file -i *.cearlyoom-1.8.2/contrib/libproc2-test/libproc2-test.c000066400000000000000000000013361461650044000223420ustar00rootroot00000000000000#include #include #include int main(int argc, char* argv[]) { enum pids_item items[] = { PIDS_STATE }; struct pids_info* info; struct pids_stack* stack; struct pids_fetch* fetched; int ret = procps_pids_new(&info, items, 1); if (ret != 0) { printf("new failured, ret=%d\n", ret); exit(4); } unsigned pidlist[] = { 1 }; fetched = procps_pids_select(info, pidlist, 1, PIDS_SELECT_PID_THREADS); if (!fetched) { printf("select error\n"); exit(3); } if (fetched->counts->total != 1) { exit(2); } stack = fetched->stacks[0]; char state = PIDS_VAL(0, s_ch, stack, info); printf("%c\n", state); }earlyoom-1.8.2/contrib/membomb/000077500000000000000000000000001461650044000164275ustar00rootroot00000000000000earlyoom-1.8.2/contrib/membomb/.gitignore000066400000000000000000000000341461650044000204140ustar00rootroot00000000000000/membomb /membomb.subthread earlyoom-1.8.2/contrib/membomb/Makefile000066400000000000000000000005631461650044000200730ustar00rootroot00000000000000CFLAGS += -Wall -Wextra -g -fstack-protector-all -std=gnu99 .PHONY: all all: membomb membomb.subthread membomb : $(wildcard *.c *.h) Makefile $(CC) $(CFLAGS) -o $@ eat_all_memory.c membomb.c membomb.subthread: $(wildcard *.c *.h) Makefile $(CC) $(CFLAGS) -lpthread -o $@ eat_all_memory.c membomb.subthread.c .PHONY: format format: clang-format --style=file -i *.c earlyoom-1.8.2/contrib/membomb/eat_all_memory.c000066400000000000000000000025471461650044000215740ustar00rootroot00000000000000#include #include #include #include #include #include #define NUM_PAGES 10 static void handle_sigterm(int sig) { printf("blocking SIGTERM %d\n", sig); } void eat_all_memory() { long page_size = sysconf(_SC_PAGESIZE); long bs = page_size * NUM_PAGES; long cnt = 0, last_sum = 0; struct timeval tv1; signal(SIGTERM, handle_sigterm); gettimeofday(&tv1, NULL); while (1) { char* 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.8.2/contrib/membomb/eat_all_memory.h000066400000000000000000000000271461650044000215700ustar00rootroot00000000000000void eat_all_memory(); earlyoom-1.8.2/contrib/membomb/membomb.c000066400000000000000000000004751461650044000202170ustar00rootroot00000000000000// 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 "eat_all_memory.h" int main() { eat_all_memory(); } earlyoom-1.8.2/contrib/membomb/membomb.subthread.c000066400000000000000000000012241461650044000221700ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* In a subthread, eat up all memory. * The main thread exits and will show up as a zombie. * * This file is part of the earlyoom project: https://github.com/rfjakob/earlyoom */ #define _GNU_SOURCE #include #include #include #include "eat_all_memory.h" void* eat_all_memory_thread(__attribute__((__unused__)) void* arg) { printf("sub thread = pid %d\n", gettid()); eat_all_memory(); return NULL; } int main() { printf("main thread = pid %d\n", gettid()); pthread_t thread; pthread_create(&thread, NULL, &eat_all_memory_thread, NULL); pthread_exit(NULL); } earlyoom-1.8.2/contrib/memforkbomb.sh000077500000000000000000000010211461650044000176420ustar00rootroot00000000000000#!/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.8.2/contrib/mon.sh000077500000000000000000000035211461650044000161420ustar00rootroot00000000000000#!/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.8.2/contrib/oomstat/000077500000000000000000000000001461650044000164775ustar00rootroot00000000000000earlyoom-1.8.2/contrib/oomstat/.gitignore000066400000000000000000000000101461650044000204560ustar00rootroot00000000000000oomstat earlyoom-1.8.2/contrib/oomstat/Makefile000066400000000000000000000000431461650044000201340ustar00rootroot00000000000000.PHONY: oomstat oomstat: go build earlyoom-1.8.2/contrib/oomstat/README.md000066400000000000000000000142041461650044000177570ustar00rootroot00000000000000# 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.8.2/contrib/oomstat/loadshift.txt000066400000000000000000000511451461650044000212230ustar00rootroot00000000000000# 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.8.2/contrib/oomstat/ontheedge.txt000066400000000000000000000402011461650044000211770ustar00rootroot00000000000000 | /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.8.2/contrib/oomstat/oomstat.go000066400000000000000000000063401461650044000205170ustar00rootroot00000000000000package 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.8.2/contrib/oomstat/overload.txt000066400000000000000000000566251461650044000210710ustar00rootroot00000000000000 | /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.8.2/contrib/utf8_membomb.sh000077500000000000000000000007221461650044000177350ustar00rootroot00000000000000#!/bin/bash # Testcase for https://github.com/rfjakob/earlyoom/issues/110 # # earlyoom output should look like this: # # sending SIGTERM to process 28570 "tail_😀😀": oom_score 629, VmRSS 15048 MiB # # and not like this: # # sending SIGTERM to process 28491 "tail_😀😀�": oom_score 630, VmRSS 15076 MiB set -eu cd $(mktemp -d) TAIL=$(which tail) ln -s $TAIL tail_😀😀😀😀😀😀😀😀 exec ./tail_😀😀😀😀😀😀😀😀 /dev/zero earlyoom-1.8.2/contrib/zombie.c000066400000000000000000000013671461650044000164510ustar00rootroot00000000000000/* 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 */ printf("zombie created, pid %d. Sleeping 10 minutes.\n", pid); sleep(600); int wstatus; wait(&wstatus); exit(0); } else { /* child */ exit(0); } } earlyoom-1.8.2/earlyoom.default000066400000000000000000000010631461650044000165460ustar00rootroot00000000000000# 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 minute instead of every hour # EARLYOOM_ARGS="-r 60" # 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.8.2/earlyoom.initscript.in000077500000000000000000000106641461650044000177310ustar00rootroot00000000000000#! /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.8.2/earlyoom.service.in000066400000000000000000000024741461650044000171760ustar00rootroot00000000000000[Unit] Description=Early OOM Daemon Documentation=man:earlyoom(1) https://github.com/rfjakob/earlyoom [Service] EnvironmentFile=-:SYSCONFDIR:/default/earlyoom ExecStart=:TARGET:/earlyoom $EARLYOOM_ARGS # Allow killing processes and calling mlockall() AmbientCapabilities=CAP_KILL CAP_IPC_LOCK CapabilityBoundingSet=CAP_KILL CAP_IPC_LOCK # Give priority to our process Nice=-20 # Avoid getting killed by OOM OOMScoreAdjust=-100 # 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 # Hardening. Deny everything we don't use. # Run as an unprivileged user with random user id. DynamicUser=true # We don't need write access anywhere. ProtectSystem=strict # We don't need /home at all, make it inaccessible. ProtectHome=true PrivateDevices=true ProtectClock=true ProtectHostname=true ProtectKernelLogs=true ProtectKernelModules=true ProtectKernelTunables=true ProtectControlGroups=true RestrictNamespaces=true RestrictRealtime=true LockPersonality=true PrivateNetwork=true IPAddressDeny=any # Unix socket is used by dbus-send. RestrictAddressFamilies=AF_UNIX SystemCallArchitectures=native SystemCallFilter=@system-service process_mrelease SystemCallFilter=~@privileged [Install] WantedBy=multi-user.target earlyoom-1.8.2/globals.c000066400000000000000000000001771461650044000151450ustar00rootroot00000000000000int enable_debug = 0; // This variable exists so the tests can point // it to a mockup proc dir char* procdir_path = "/proc"; earlyoom-1.8.2/globals.h000066400000000000000000000002051461650044000151420ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef GLOBALS_H #define GLOBALS_H extern int enable_debug; extern char* procdir_path; #endif earlyoom-1.8.2/go.mod000066400000000000000000000002341461650044000144560ustar00rootroot00000000000000module github.com/rfjakob/earlyoom go 1.16 require ( github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect golang.org/x/sys v0.6.0 ) earlyoom-1.8.2/go.sum000066400000000000000000000005701461650044000145060ustar00rootroot00000000000000github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 h1:SjZ2GvvOononHOpK84APFuMvxqsk3tEIaKH/z4Rpu3g= github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= earlyoom-1.8.2/kill.c000066400000000000000000000432041461650044000144530ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* Kill the most memory-hungy process */ #include #include #include #include #include #include #include #include #include /* Definition of SYS_* constants */ #include #include #include "globals.h" #include "kill.h" #include "meminfo.h" #include "msg.h" // Processes matching "--prefer REGEX" get OOM_SCORE_PREFER added to their oom_score #define OOM_SCORE_PREFER 300 // Processes matching "--avoid REGEX" get OOM_SCORE_AVOID added to their oom_score #define OOM_SCORE_AVOID -300 // Processes matching "--prefer REGEX" get VMRSS_PREFER added to their VmRSSkiB #define VMRSS_PREFER 3145728 // Processes matching "--avoid REGEX" get VMRSS_AVOID added to their VmRSSkiB #define VMRSS_AVOID -3145728 // Buffer size for UID/GID/PID string conversion #define UID_BUFSIZ 128 // At most 1 notification per second when --dryrun is active #define NOTIFY_RATELIMIT 1 static bool isnumeric(char* str) { int i = 0; // Empty string is not numeric if (str[0] == 0) return false; while (1) { if (str[i] == 0) // End of string return true; if (isdigit(str[i]) == 0) return false; i++; } } static void notify_dbus(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("%s: exec failed: %s\n", __func__, strerror(errno)); exit(1); } static void notify_ext(const char* script, const procinfo_t* victim) { pid_t pid1 = fork(); if (pid1 == -1) { warn("notify_ext: fork() returned -1: %s\n", strerror(errno)); return; } else if (pid1 != 0) { return; } char pid_str[UID_BUFSIZ] = { 0 }; char uid_str[UID_BUFSIZ] = { 0 }; snprintf(pid_str, UID_BUFSIZ, "%d", victim->pid); snprintf(uid_str, UID_BUFSIZ, "%d", victim->uid); setenv("EARLYOOM_PID", pid_str, 1); setenv("EARLYOOM_UID", uid_str, 1); setenv("EARLYOOM_NAME", victim->name, 1); setenv("EARLYOOM_CMDLINE", victim->cmdline, 1); execl(script, script, NULL); warn("%s: exec %s failed: %s\n", __func__, script, strerror(errno)); exit(1); } static void notify_process_killed(const poll_loop_args_t* args, const procinfo_t* victim) { // Dry run can cause the notify function to be called on each poll as // nothing is immediately done to change the situation we don't know how // heavy the notify script is so avoid spamming it if (args->dryrun) { static struct timespec prev_notify = { 0 }; struct timespec cur_time = { 0 }; int ret = clock_gettime(CLOCK_MONOTONIC, &cur_time); if (ret == -1) { warn("%s: clock_gettime failed: %s\n", __func__, strerror(errno)); return; } // Ignores nanoseconds, but good enough here if (cur_time.tv_sec - prev_notify.tv_sec < NOTIFY_RATELIMIT) { // Too soon debug("%s: rate limit hit, skipping notifications this time\n", __func__); return; } prev_notify = cur_time; } if (args->notify) { char notif_args[PATH_MAX + 1000]; snprintf(notif_args, sizeof(notif_args), "Low memory! Killing process %d %s", victim->pid, victim->name); notify_dbus("earlyoom", notif_args); } if (args->notify_ext) { notify_ext(args->notify_ext, victim); } } #if defined(__NR_pidfd_open) && defined(__NR_process_mrelease) #define HAVE_MRELEASE #else #warning process_mrelease is not supported. earlyoom will still work but with degraded performance. #endif // kill_release kills a process and calls process_mrelease to // release the memory as quickly as possible. // // See https://lwn.net/Articles/864184/ for details on process_mrelease. int kill_release(const pid_t pid, const int pidfd, const int sig) { int res = kill(pid, sig); if (res != 0) { return res; } // Can't do process_mrelease without a pidfd. if (pidfd < 0) { return 0; } #if defined(HAVE_MRELEASE) res = (int)syscall(__NR_process_mrelease, pidfd, 0); if (res != 0) { warn("%s: pid=%d: process_mrelease pidfd=%d failed: %s\n", __func__, pid, pidfd, strerror(errno)); } else { info("%s: pid=%d: process_mrelease pidfd=%d success\n", __func__, pid, pidfd); } #endif // Return 0 regardless of process_mrelease outcome return 0; } /* * 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) { const unsigned poll_ms = 100; int pidfd = -1; if (args->dryrun && sig != 0) { warn("dryrun, not actually sending any signal\n"); return 0; } if (args->kill_process_group) { int res = getpgid(pid); if (res < 0) { return res; } pid = -res; warn("killing whole process group %d (-g flag is active)\n", res); } #if defined(HAVE_MRELEASE) // Open the pidfd *before* calling kill(). if (!args->kill_process_group && sig != 0) { pidfd = (int)syscall(__NR_pidfd_open, pid, 0); if (pidfd < 0) { warn("%s pid %d: error opening pidfd: %s\n", __func__, pid, strerror(errno)); } } #else warn("%s pid %d: system does not support process_mrelease, skipping\n", __func__, pid); #endif int res = kill_release(pid, pidfd, sig); if (res != 0) { goto out_close; } /* signal 0 does not kill the process. Don't wait for it to exit */ if (sig == 0) { goto out_close; } struct timespec t0 = { 0 }; clock_gettime(CLOCK_MONOTONIC, &t0); for (unsigned i = 0; i < 100; i++) { struct timespec t1 = { 0 }; clock_gettime(CLOCK_MONOTONIC, &t1); float secs = (float)(t1.tv_sec - t0.tv_sec) + (float)(t1.tv_nsec - t0.tv_nsec) / (float)1e9; // We have sent SIGTERM but now have dropped below SIGKILL limits. // Escalate to SIGKILL. if (sig != SIGKILL) { meminfo_t m = parse_meminfo(); print_mem_stats(debug, m); if (m.MemAvailablePercent <= args->mem_kill_percent && m.SwapFreePercent <= args->swap_kill_percent) { sig = SIGKILL; warn("escalating to SIGKILL after %.3f seconds\n", secs); res = kill_release(pid, pidfd, sig); if (res != 0) { goto out_close; } } } else if (enable_debug) { meminfo_t m = parse_meminfo(); print_mem_stats(info, m); } if (!is_alive(pid)) { warn("process %d exited after %.3f seconds\n", pid, secs); goto out_close; } struct timespec req = { .tv_sec = (time_t)(poll_ms / 1000), .tv_nsec = (poll_ms % 1000) * 1000000 }; nanosleep(&req, NULL); } res = -1; errno = ETIME; warn("process %d did not exit\n", pid); out_close: if (pidfd >= 0) { int saved_errno = errno; if (close(pidfd)) { warn("%s pid %d: error closing pidfd %d: %s\n", __func__, pid, pidfd, strerror(errno)); } errno = saved_errno; } return res; } // is_larger finds out if the process with pid `cur->pid` uses more memory // than our current `victim`. // In the process, it fills the `cur` structure. It does so lazily, meaning // it only fills the fields it needs to make a decision. bool is_larger(const poll_loop_args_t* args, const procinfo_t* victim, procinfo_t* cur) { if (cur->pid <= 2) { // Let's not kill init or kthreadd. return false; } // Ignore processes owned by root user? if (args->ignore_root_user) { int res = get_uid(cur->pid); if (res < 0) { debug("%s: pid %d: error reading uid: %s\n", __func__, cur->pid, strerror(-res)); return false; } cur->uid = res; if (cur->uid == 0) { return false; } } { bool res = parse_proc_pid_stat(&cur->stat, cur->pid); if (!res) { debug("%s: pid %d: error reading stat\n", __func__, cur->pid); return false; } const long page_size = sysconf(_SC_PAGESIZE); cur->VmRSSkiB = cur->stat.rss * page_size / 1024; } // A pid is a kernel thread if it's pid or ppid is 2. // At least that's what procs does: // https://github.com/warmchang/procps/blob/d173f5d6db746e3f252a6182aa1906a292fc200f/library/readproc.c#L1325 // // The check for pid == 2 has already been done at the top. if (cur->stat.ppid == 2) { return false; } { int res = get_oom_score(cur->pid); if (res < 0) { debug("%s: pid %d: error reading oom_score: %s\n", __func__, cur->pid, strerror(-res)); return false; } cur->oom_score = res; } if ((args->prefer_regex || args->avoid_regex || args->ignore_regex)) { int res = get_comm(cur->pid, cur->name, sizeof(cur->name)); if (res < 0) { debug("%s: pid %d: error reading process name: %s\n", __func__, cur->pid, strerror(-res)); return false; } if (args->prefer_regex && regexec(args->prefer_regex, cur->name, (size_t)0, NULL, 0) == 0) { if (args->sort_by_rss) { cur->VmRSSkiB += VMRSS_PREFER; } else { cur->oom_score += OOM_SCORE_PREFER; } } if (args->avoid_regex && regexec(args->avoid_regex, cur->name, (size_t)0, NULL, 0) == 0) { if (args->sort_by_rss) { cur->VmRSSkiB += VMRSS_AVOID; } else { cur->oom_score += OOM_SCORE_AVOID; } } if (args->ignore_regex && regexec(args->ignore_regex, cur->name, (size_t)0, NULL, 0) == 0) { return false; } } // find process with the largest rss if (args->sort_by_rss) { // Case 1: neither victim nor cur have rss=0 (zombie main thread). // This is the usual case. if (cur->VmRSSkiB > 0 && victim->VmRSSkiB > 0) { if (cur->VmRSSkiB < victim->VmRSSkiB) { return false; } if (cur->VmRSSkiB == victim->VmRSSkiB && cur->oom_score <= victim->oom_score) { return false; } } // Case 2: one (or both) have rss=0 (zombie main thread) else { if (cur->VmRSSkiB == 0) { // only print the warning when the zombie is first seen, i.e. as "cur" get_comm(cur->pid, cur->name, sizeof(cur->name)); warn("%s: pid %d \"%s\": rss=0 but oom_score=%d. Zombie main thread? Using oom_score for this process.\n", __func__, cur->pid, cur->name, cur->oom_score); } if (cur->oom_score < victim->oom_score) { return false; } if (cur->oom_score == victim->oom_score && cur->VmRSSkiB <= victim->VmRSSkiB) { return false; } } } else { /* find process with the largest oom_score */ if (cur->oom_score < victim->oom_score) { return false; } if (cur->oom_score == victim->oom_score && cur->VmRSSkiB <= victim->VmRSSkiB) { return false; } } // Skip processes with oom_score_adj = -1000, like the // kernel oom killer would. { int res = get_oom_score_adj(cur->pid, &cur->oom_score_adj); if (res < 0) { debug("%s: pid %d: error reading oom_score_adj: %s\n", __func__, cur->pid, strerror(-res)); return false; } if (cur->oom_score_adj == -1000) { return false; } } return true; } // Fill the fields in `cur` that are not required for the kill decision. // Used to log details about the selected process. void fill_informative_fields(procinfo_t* cur) { if (strlen(cur->name) == 0) { int res = get_comm(cur->pid, cur->name, sizeof(cur->name)); if (res < 0) { debug("%s: pid %d: error reading process name: %s\n", __func__, cur->pid, strerror(-res)); } } if (strlen(cur->cmdline) == 0) { int res = get_cmdline(cur->pid, cur->cmdline, sizeof(cur->cmdline)); if (res < 0) { debug("%s: pid %d: error reading process cmdline: %s\n", __func__, cur->pid, strerror(-res)); } } if (cur->uid == PROCINFO_FIELD_NOT_SET) { int res = get_uid(cur->pid); if (res < 0) { debug("%s: pid %d: error reading uid: %s\n", __func__, cur->pid, strerror(-res)); } else { cur->uid = res; } } } // debug_print_procinfo pretty-prints the process information in `cur`. void debug_print_procinfo(procinfo_t* cur) { if (!enable_debug) { return; } fill_informative_fields(cur); debug("%5d %9d %7lld %5d %13d \"%s\"", cur->pid, cur->oom_score, cur->VmRSSkiB, cur->uid, cur->oom_score_adj, cur->name); } void debug_print_procinfo_header() { debug(" PID OOM_SCORE RSSkiB UID OOM_SCORE_ADJ COMM\n"); } /* * Find the process with the largest oom_score or rss(when flag --sort-by-rss is set). */ procinfo_t find_largest_process(const poll_loop_args_t* args) { DIR* procdir = opendir(procdir_path); 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); } debug_print_procinfo_header(); const procinfo_t empty_procinfo = { .pid = PROCINFO_FIELD_NOT_SET, .uid = PROCINFO_FIELD_NOT_SET, .oom_score = PROCINFO_FIELD_NOT_SET, .oom_score_adj = PROCINFO_FIELD_NOT_SET, .VmRSSkiB = PROCINFO_FIELD_NOT_SET, /* omitted fields are set to zero */ }; procinfo_t victim = empty_procinfo; 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 = empty_procinfo; cur.pid = (int)strtol(d->d_name, NULL, 10); bool larger = is_larger(args, &victim, &cur); debug_print_procinfo(&cur); if (larger) { debug(" <--- new victim\n"); victim = cur; } else { debug("\n"); } } 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); // zero victim struct victim = (const procinfo_t) { 0 }; } if (victim.pid >= 0) { // We will pretty-print the victim later, so get all the info. fill_informative_fields(&victim); } 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_dbus("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 notify the user. if (sig != 0 || enable_debug) { warn("sending %s to process %d uid %d \"%s\": oom_score %d, VmRSS %lld MiB, cmdline \"%s\"\n", sig_name, victim->pid, victim->uid, victim->name, victim->oom_score, victim->VmRSSkiB / 1024, victim->cmdline); } 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) { notify_process_killed(args, victim); } if (sig == 0) { return; } if (res != 0) { warn("kill failed: %s\n", strerror(saved_errno)); if (args->notify) { notify_dbus("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.8.2/kill.h000066400000000000000000000024521461650044000144600ustar00rootroot00000000000000/* 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; /* send d-bus notifications? */ bool notify; /* Path to script for programmatic notifications (or NULL) */ char* notify_ext; /* kill all processes within a process group */ bool kill_process_group; /* do not kill processes owned by root */ bool ignore_root_user; /* find process with the largest rss */ bool sort_by_rss; /* prefer/avoid killing these processes. NULL = no-op. */ regex_t* prefer_regex; regex_t* avoid_regex; /* will ignore these processes. NULL = no-op. */ regex_t* ignore_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, const procinfo_t* victim); procinfo_t find_largest_process(const poll_loop_args_t* args); bool is_larger(const poll_loop_args_t* args, const procinfo_t* victim, procinfo_t* cur); #endif earlyoom-1.8.2/main.c000066400000000000000000000467031461650044000144530ustar00rootroot00000000000000// 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 #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, LONG_OPT_IGNORE, LONG_OPT_IGNORE_ROOT, LONG_OPT_USE_SYSLOG, LONG_OPT_SORT_BY_RSS, }; 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; } // Dry-run oom kill to make sure that // (1) it works (meaning /proc is accessible) // (2) the stack grows to maximum size before calling mlockall() static void startup_selftests(poll_loop_args_t* args) { { debug("%s: dry-running oom kill...\n", __func__); procinfo_t victim = find_largest_process(args); kill_process(args, 0, &victim); } if (args->notify_ext) { if (args->notify_ext[0] != '/') { warn("%s: -N: notify script '%s' is not an absolute path, disabling -N\n", __func__, args->notify_ext); args->notify_ext = NULL; } else if (access(args->notify_ext, X_OK)) { warn("%s: -N: notify script '%s' is not executable: %s\n", __func__, args->notify_ext, strerror(errno)); } } #ifdef PROFILE_FIND_LARGEST_PROCESS struct timespec t0 = { 0 }, t1 = { 0 }; clock_gettime(CLOCK_MONOTONIC, &t0); warn("PROFILE_FIND_LARGEST_PROCESS: looping forever on find_largest_process(). Use sysprof of perf to capture profile.\n"); long i = 0; while (1) { find_largest_process(args); i++; const int avg_n = 1000; if (i % avg_n == 0) { clock_gettime(CLOCK_MONOTONIC, &t1); long delta_usecs = (t1.tv_sec - t0.tv_sec) * 1000000 + (t1.tv_nsec - t0.tv_nsec) / 1000; double avg_wall_time_ms = (double)(delta_usecs / avg_n) / 1000.0; info("average find_largest_process() wall time: %.3lf ms\n", avg_wall_time_ms); clock_gettime(CLOCK_MONOTONIC, &t0); } }; #endif } 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, .ignore_root_user = false, .sort_by_rss = false, /* omitted fields are set to zero */ }; int set_my_priority = 0; char* prefer_cmds = NULL; char* avoid_cmds = NULL; char* ignore_cmds = NULL; regex_t _prefer_regex; regex_t _avoid_regex; regex_t _ignore_regex; /* request line buffering for stdout - otherwise the output * may lag behind stderr */ setlinebuf(stdout); /* clean up dbus-send zombies */ signal(SIGCHLD, SIG_IGN); fprintf(stderr, "earlyoom " VERSION "\n"); if (chdir(procdir_path) != 0) { fatal(4, "Could not cd to /proc: %s", strerror(errno)); } // PR_CAP_AMBIENT is not available on kernel < 4.3 #ifdef PR_CAP_AMBIENT // When systemd starts a daemon with capabilities, it uses ambient // capabilities to do so. If not dropped, the capabilities can spread // to any child process. This is usually not necessary and its a good // idea to drop them if not needed. prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); #endif meminfo_t m = parse_meminfo(); int c; const char* short_opt = "m:s:M:S:kingN:dvr:ph"; struct option long_opt[] = { { "prefer", required_argument, NULL, LONG_OPT_PREFER }, { "avoid", required_argument, NULL, LONG_OPT_AVOID }, { "ignore", required_argument, NULL, LONG_OPT_IGNORE }, { "dryrun", no_argument, NULL, LONG_OPT_DRYRUN }, { "ignore-root-user", no_argument, NULL, LONG_OPT_IGNORE_ROOT }, { "sort-by-rss", no_argument, NULL, LONG_OPT_SORT_BY_RSS }, { "syslog", no_argument, NULL, LONG_OPT_USE_SYSLOG }, { "help", no_argument, NULL, 'h' }, { "debug", no_argument, NULL, 'd' }, { 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': fprintf(stderr, "Option -i is ignored since earlyoom v1.7\n"); break; case 'n': args.notify = true; fprintf(stderr, "Notifying through D-Bus\n"); break; case 'g': args.kill_process_group = true; break; case 'N': args.notify_ext = 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_IGNORE_ROOT: args.ignore_root_user = true; fprintf(stderr, "Processes owned by root will not be killed\n"); break; case LONG_OPT_SORT_BY_RSS: args.sort_by_rss = true; fprintf(stderr, "Find process with the largest rss\n"); 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 LONG_OPT_USE_SYSLOG: earlyoom_syslog_init(); break; case LONG_OPT_IGNORE: ignore_cmds = optarg; 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" " -n enable d-bus notifications\n" " -N /PATH/TO/SCRIPT call script after oom kill\n" " -g kill all processes within a process group\n" " -d, --debug 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" " --ignore-root-user do not kill processes owned by root\n" " --sort-by-rss find process with the largest rss (default oom_score)\n" " --prefer REGEX prefer to kill processes matching REGEX\n" " --avoid REGEX avoid killing processes matching REGEX\n" " --ignore REGEX ignore processes matching REGEX\n" " --dryrun dry run (do not kill any processes)\n" " --syslog use syslog instead of std streams\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 (ignore_cmds) { args.ignore_regex = &_ignore_regex; if (regcomp(args.ignore_regex, ignore_cmds, REG_EXTENDED | REG_NOSUB) != 0) { fatal(6, "could not compile regexp '%s'\n", ignore_cmds); } fprintf(stderr, "Will ignore process names that match regex '%s'\n", ignore_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, user mem total: %4lld MiB, swap total: %4lld MiB\n", m.MemTotalKiB / 1024, m.UserMemTotalKiB / 1024, m.SwapTotalKiB / 1024); fprintf(stderr, "sending SIGTERM when mem avail <= " PRIPCT " and swap free <= " PRIPCT ",\n", args.mem_term_percent, args.swap_term_percent); fprintf(stderr, " SIGKILL when mem avail <= " PRIPCT " and swap free <= " PRIPCT "\n", args.mem_kill_percent, args.swap_kill_percent); startup_selftests(&args); 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[PATH_LEN] = { 0 }; pid_t pid = getpid(); snprintf(buf, sizeof(buf), "%s/%d/oom_score_adj", procdir_path, 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) * (double)m->UserMemTotalKiB / 100); if (mem_headroom_kib < 0) { mem_headroom_kib = 0; } long long swap_headroom_kib = (long long)((m->SwapFreePercent - args->swap_term_percent) * (double)m->SwapTotalKiB / 100); 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(info, 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 }; while (nanosleep(&req, &req) == -1 && errno == EINTR) ; report_countdown_ms -= (int)sleep_ms; } } earlyoom-1.8.2/meminfo.c000066400000000000000000000202301461650044000151440ustar00rootroot00000000000000// SPDX-License-Identifier: MIT /* Parse /proc/meminfo * Returned values are in kiB */ #include #include #include // for size_t #include #include #include #include #include #include "globals.h" #include "meminfo.h" #include "msg.h" #include "proc_pid.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) { char buf[PATH_LEN] = { 0 }; snprintf(buf, sizeof(buf), "%s/%s", procdir_path, "meminfo"); fd = fopen(buf, "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); m.AnonPagesKiB = get_entry_fatal("AnonPages:", buf); m.SwapFreeKiB = get_entry_fatal("SwapFree:", buf); m.MemAvailableKiB = get_entry("MemAvailable:", buf); if (m.MemAvailableKiB < 0) { m.MemAvailableKiB = 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; } } // Calculated values m.UserMemTotalKiB = m.MemAvailableKiB + m.AnonPagesKiB; // Calculate percentages m.MemAvailablePercent = (double)m.MemAvailableKiB * 100 / (double)m.UserMemTotalKiB; if (m.SwapTotalKiB > 0) { m.SwapFreePercent = (double)m.SwapFreeKiB * 100 / (double)m.SwapTotalKiB; } else { m.SwapFreePercent = 0; } return m; } bool is_alive(int pid) { // whole process group (-g flag)? if (pid < 0) { // signal 0 does nothing, but we do get an error when the process // group does not exist. int res = kill(pid, 0); if (res == 0) { return true; } return false; } pid_stat_t stat; if (!parse_proc_pid_stat(&stat, pid)) { return false; } debug("%s: state=%c num_threads=%ld\n", __func__, stat.state, stat.num_threads); if (stat.state == 'Z' && stat.num_threads == 1) { // A zombie process without subthreads 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 (success) 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), "%s/%d/%s", procdir_path, 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), "%s/%d/comm", procdir_path, 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 space out[n - 1] = 0; fix_truncated_utf8(out); return 0; } /* Read /proc/[pid]/cmdline (process command line truncated to 256 bytes). * The null bytes are replaced by space. * Returns 0 on success and -errno on error. */ int get_cmdline(int pid, char* out, size_t outlen) { char path[PATH_LEN] = { 0 }; snprintf(path, sizeof(path), "%s/%d/cmdline", procdir_path, 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_cmdline: fread() failed"); fclose(f); return -fread_errno; } fclose(f); /* replace null character with space */ for (size_t i = 0; i < n; i++) { if (out[i] == '\0') { out[i] = ' '; } } // Strip trailing space 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), "%s/%d", procdir_path, pid); struct stat st = { 0 }; int res = stat(path, &st); if (res < 0) { return -errno; } return (int)st.st_uid; } /* 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.MemAvailableKiB / 1024, m.UserMemTotalKiB / 1024, m.MemAvailablePercent, m.SwapFreeKiB / 1024, m.SwapTotalKiB / 1024, m.SwapFreePercent); } earlyoom-1.8.2/meminfo.h000066400000000000000000000024751461650044000151640ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef MEMINFO_H #define MEMINFO_H #define PATH_LEN 256 #include "proc_pid.h" #include typedef struct { // Values from /proc/meminfo, in KiB long long MemTotalKiB; long long MemAvailableKiB; long long SwapTotalKiB; long long SwapFreeKiB; long long AnonPagesKiB; // Calculated values // UserMemTotalKiB = MemAvailableKiB + AnonPagesKiB. // Represents the total amount of memory that may be used by user processes. long long UserMemTotalKiB; // 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 oom_score; int oom_score_adj; long long VmRSSkiB; pid_stat_t stat; char name[PATH_LEN]; char cmdline[PATH_LEN]; } procinfo_t; // placeholder value for numeric fields #define PROCINFO_FIELD_NOT_SET -9999 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); int get_comm(int pid, char* out, size_t outlen); int get_uid(int pid); int get_cmdline(int pid, char* out, size_t outlen); #endif earlyoom-1.8.2/msg.c000066400000000000000000000153201461650044000143040ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include #include #include #include #include // need strlen() #include #include #include "globals.h" #include "msg.h" static int use_syslog = 0; // color_log writes 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) { // Find out (and cache) if we should use color static int stdout_is_tty = -1; static int stderr_is_tty = -1; static int no_color = -1; bool is_tty = false; if (no_color == -1) { // https://no-color.org/ if (getenv("NO_COLOR") != NULL) { no_color = 1; } else { no_color = 0; } } if (no_color == 0) { if (fileno(f) == fileno(stdout)) { if (stdout_is_tty == -1) { stdout_is_tty = isatty(fileno(stdout)); } is_tty = stdout_is_tty; } else if (fileno(f) == fileno(stderr)) { if (stderr_is_tty == -1) { stderr_is_tty = isatty(fileno(stderr)); } is_tty = stderr_is_tty; } } // fds other than stdout and stderr never get color const char* reset = "\033[0m"; if (!is_tty) { 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); } } static void logger(int priority, FILE* f, const char* color, const char* fmt, va_list vl) { if (use_syslog) { vsyslog(priority, fmt, vl); } else { color_log(f, color, fmt, vl); } } void earlyoom_syslog_init(void) { if (!use_syslog) { use_syslog = 1; openlog("earlyoom", LOG_PID | LOG_NDELAY, LOG_DAEMON); atexit(closelog); } } // 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); logger(LOG_ERR, 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); logger(LOG_WARNING, 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); logger(LOG_DEBUG, stdout, gray, fmt, vl); va_end(vl); return 0; } // Print info message to stdout. No prefix is added. int info(const char* fmt, ...) { va_list vl; va_start(vl, fmt); logger(LOG_INFO, stdout, "", 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), "conversion 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.8.2/msg.h000066400000000000000000000020031461650044000143030ustar00rootroot00000000000000/* 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))); int info(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); void earlyoom_syslog_init(); #endif earlyoom-1.8.2/proc_pid.c000066400000000000000000000051721461650044000153210ustar00rootroot00000000000000#include #include #include #include #include "globals.h" #include "msg.h" #include "proc_pid.h" // Parse a buffer that contains the text from /proc/$pid/stat. Example: // $ cat /proc/self/stat // 551716 (cat) R 551087 551716 551087 34816 551716 4194304 94 0 0 0 0 0 0 0 20 0 1 0 5017160 227065856 448 18446744073709551615 94898152189952 94898152206609 140721104501216 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 94898152221328 94898152222824 94898185641984 140721104505828 140721104505848 140721104505848 140721104510955 0 bool parse_proc_pid_stat_buf(pid_stat_t* out, char* buf) { char* closing_bracket = strrchr(buf, ')'); if (!closing_bracket) { return false; } // If the string ends (i.e. has a null byte) after the closing bracket: bail out. if (!closing_bracket[1]) { return false; } // Because of the check above, there must be at least one more byte at // closing_bracket[2] (possibly a null byte, but sscanf will handle that). char* state_field = &closing_bracket[2]; int ret = sscanf(state_field, "%c " // state "%d %*d %*d %*d %*d " // ppid, pgrp, sid, tty_nr, tty_pgrp "%*u %*u %*u %*u %*u " // flags, min_flt, cmin_flt, maj_flt, cmaj_flt "%*u %*u %*u %*u " // utime, stime, cutime, cstime "%*d %*d " // priority, nice "%ld " // num_threads "%*d %*d %*d" // itrealvalue, starttime, vsize "%ld ", // rss &out->state, &out->ppid, &out->num_threads, &out->rss); if (ret != 4) { return false; }; return true; }; // Read and parse /proc/$pid/stat. Returns true on success, false on error. bool parse_proc_pid_stat(pid_stat_t* out, int pid) { // Largest /proc/*/stat file here is 363 bytes acc. to: // wc -c /proc/*/stat | sort // 512 seems safe given that we only need the first 20 fields. char buf[512] = { 0 }; // Read /proc/$pid/stat snprintf(buf, sizeof(buf), "%s/%d/stat", procdir_path, pid); FILE* f = fopen(buf, "r"); if (f == NULL) { // Process is gone - good. return false; } memset(buf, 0, sizeof(buf)); // File content looks like this: // 10751 (cat) R 2663 10751 2663[...] // File may be bigger than 256 bytes, but we only need the first 20 or so. int len = (int)fread(buf, 1, sizeof(buf) - 1, f); bool read_error = ferror(f) || len == 0; fclose(f); if (read_error) { warn("%s: fread failed: %s\n", __func__, strerror(errno)); return false; } // Terminate string at end of data buf[len] = 0; return parse_proc_pid_stat_buf(out, buf); } earlyoom-1.8.2/proc_pid.h000066400000000000000000000004701461650044000153220ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef PROC_PID_H #define PROC_PID_H #include typedef struct { char state; int ppid; long num_threads; long rss; } pid_stat_t; bool parse_proc_pid_stat_buf(pid_stat_t* out, char* buf); bool parse_proc_pid_stat(pid_stat_t* out, int pid); #endif earlyoom-1.8.2/testsuite_c_wrappers.go000066400000000000000000000052751461650044000201670ustar00rootroot00000000000000package earlyoom_testsuite import ( "fmt" "strings" ) // #cgo CFLAGS: -std=gnu99 -DCGO // #include "meminfo.h" // #include "kill.h" // #include "msg.h" // #include "globals.h" // #include "proc_pid.h" import "C" func init() { C.enable_debug = 1 } func enable_debug(state bool) (oldState bool) { if C.enable_debug == 1 { oldState = true } if state { C.enable_debug = 1 } else { C.enable_debug = 0 } return } 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() } // Wrapper so _test.go code can create a poll_loop_args_t // struct. _test.go code cannot use C. func poll_loop_args_t(sort_by_rss bool) (args C.poll_loop_args_t) { args.sort_by_rss = C.bool(sort_by_rss) return } func procinfo_t() C.procinfo_t { return C.procinfo_t{} } func is_larger(args *C.poll_loop_args_t, victim mockProcProcess, cur mockProcProcess) bool { cVictim := victim.toProcinfo_t() cCur := cur.toProcinfo_t() return bool(C.is_larger(args, &cVictim, &cCur)) } 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_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) } func get_cmdline(pid int) (int, string) { cstr := C.CString(strings.Repeat("\000", 256)) res := C.get_cmdline(C.int(pid), cstr, 256) return int(res), C.GoString(cstr) } func procdir_path(str string) string { if str != "" { cstr := C.CString(str) C.procdir_path = cstr } return C.GoString(C.procdir_path) } func parse_proc_pid_stat_buf(buf string) (res bool, out C.pid_stat_t) { cbuf := C.CString(buf) res = bool(C.parse_proc_pid_stat_buf(&out, cbuf)) return res, out } func parse_proc_pid_stat(pid int) (res bool, out C.pid_stat_t) { res = bool(C.parse_proc_pid_stat(&out, C.int(pid))) return res, out } earlyoom-1.8.2/testsuite_cli_test.go000066400000000000000000000221451461650044000176230ustar00rootroot00000000000000package earlyoom_testsuite import ( "fmt" "io/ioutil" "math" "os" "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 parseMeminfoLine(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 = parseMeminfoLine(l) } if strings.HasPrefix(l, "SwapTotal:") { swapTotal = parseMeminfoLine(l) } } return } // earlyoom RSS should never be above 1 MiB, // but on some systems, it is (due to glibc?). // https://github.com/rfjakob/earlyoom/issues/221 // https://github.com/rfjakob/earlyoom/issues/296 const rssMaxKiB = 4096 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, --prefer, --ignore-root-user and --sort-by-rss {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{"--ignore-root-user"}, code: -1, stderrContains: "Processes owned by root will not be killed", stdoutContains: memReport}, {args: []string{"--sort-by-rss"}, code: -1, stderrContains: "Find process with the largest rss", stdoutContains: memReport}, {args: []string{"-i"}, code: -1, stderrContains: "Option -i is ignored"}, // 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 avail <= 2.00% and swap free <= 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 avail <= 10.00% and swap free <= 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 avail <= 3.14%", stdoutContains: memReport}, {args: []string{"-m", "7,3.14"}, code: -1, stderrContains: "SIGKILL when mem avail <= 3.14%", stdoutContains: memReport}, {args: []string{"-s", "12.34"}, code: -1, stderrContains: "swap free <= 12.34%", stdoutContains: memReport}, // Use both -m/-M {args: []string{"-m", "10", "-M", mem1percent}, code: -1, stderrContains: "SIGTERM when mem avail <= 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 free <= 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 > rssMaxKiB { t.Errorf("Memory usage too high! actual rss: %d, rssMax: %d", res.rss, rssMaxKiB) pass = false } if res.fds > openFdsMax { if os.Getenv("GITHUB_ACTIONS") == "true" { t.Log("Ignoring fd leak. Github Actions bug? See https://github.com/actions/runner/issues/1188") } else { 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 > rssMaxKiB { t.Errorf("rss above %d kiB", rssMaxKiB) } t.Logf("earlyoom RSS: %d kiB", res.rss) } earlyoom-1.8.2/testsuite_helpers.go000066400000000000000000000132651461650044000174620ustar00rootroot00000000000000package earlyoom_testsuite import ( "bufio" "bytes" "fmt" "io/ioutil" "log" "os" "os/exec" "syscall" "testing" "time" linuxproc "github.com/c9s/goprocinfo/linux" ) // #include "meminfo.h" import "C" 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() stat, err := linuxproc.ReadProcessStat(fmt.Sprintf("/proc/%d/stat", cmd.Process.Pid)) if err != nil { panic(err) } rss := int(stat.Rss) 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, } } /* $ ls -l /proc/$(pgrep earlyoom)/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 */ const openFdsMax = 5 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 } if len(names) > openFdsMax { fmt.Printf("countFds: earlyoom has too many open fds:\n") for _, n := range names { linkName := fmt.Sprintf("%s/%s", dir, n) linkTarget, err := os.Readlink(linkName) fmt.Printf("%s -> %s, err=%v\n", linkName, linkTarget, err) } } 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 } type mockProcProcess struct { pid int state string // set to "R" when empty oom_score int VmRSSkiB int comm string num_threads int // set to 1 when zero } func (m *mockProcProcess) toProcinfo_t() (p C.procinfo_t) { p.pid = C.int(m.pid) p.oom_score = C.int(m.oom_score) p.VmRSSkiB = C.longlong(m.VmRSSkiB) for i, v := range []byte(m.comm) { p.name[i] = C.char(v) } return p } func mockProc(t *testing.T, procs []mockProcProcess) { mockProcdir, err := ioutil.TempDir("", t.Name()) if err != nil { t.Fatal(err) } procdir_path(mockProcdir) for _, p := range procs { if p.state == "" { p.state = "R" } if p.num_threads == 0 { p.num_threads = 1 } pidDir := fmt.Sprintf("%s/%d", mockProcdir, int(p.pid)) if err := os.Mkdir(pidDir, 0755); err != nil { t.Fatal(err) } // statm // // rss = 2nd field, in pages. The other fields are not used by earlyoom. rss := p.VmRSSkiB * 1024 / os.Getpagesize() content := []byte(fmt.Sprintf("1 %d 3 4 5 6 7\n", rss)) if err := ioutil.WriteFile(pidDir+"/statm", content, 0444); err != nil { t.Fatal(err) } // stat // // Real /proc/pid/stat string for gnome-shell template := "549077 (%s) S 547891 549077 549077 0 -1 4194560 245592 104 342 5 108521 28953 0 1 20 0 %d 0 4816953 5260238848 %d 18446744073709551615 94179647238144 94179647245825 140730757359824 0 0 0 0 16781312 17656 0 0 0 17 1 0 0 0 0 0 94179647252976 94179647254904 94179672109056 140730757367876 140730757367897 140730757367897 140730757369827 0\n" content = []byte(fmt.Sprintf(template, p.comm, p.num_threads, rss)) if err := ioutil.WriteFile(pidDir+"/stat", content, 0444); err != nil { t.Fatal(err) } // oom_score content = []byte(fmt.Sprintf("%d\n", p.oom_score)) if err := ioutil.WriteFile(pidDir+"/oom_score", content, 0444); err != nil { t.Fatal(err) } // oom_score_adj if err := ioutil.WriteFile(pidDir+"/oom_score_adj", []byte("0\n"), 0444); err != nil { t.Fatal(err) } // comm if err := ioutil.WriteFile(pidDir+"/comm", []byte(p.comm+"\n"), 0444); err != nil { t.Fatal(err) } // cmdline if err := ioutil.WriteFile(pidDir+"/cmdline", []byte("foo\000-bar\000-baz"), 0444); err != nil { t.Fatal(err) } } } earlyoom-1.8.2/testsuite_unit_test.go000066400000000000000000000246241461650044000200370ustar00rootroot00000000000000package earlyoom_testsuite import ( "fmt" "io/ioutil" "os" "strings" "syscall" "testing" "unicode/utf8" linuxproc "github.com/c9s/goprocinfo/linux" ) // 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 TestIsAliveMock(t *testing.T) { mockProcdir, err := ioutil.TempDir("", t.Name()) if err != nil { t.Fatal(err) } procdir_path(mockProcdir) defer procdir_path("/proc") if err := os.Mkdir(mockProcdir+"/100", 0700); err != nil { t.Fatal(err) } statString := func(comm string, state string) string { template := "144815 (%s) %s 17620 144815 144815 34817 247882 4194304 20170 1855121 1 3321 28 46 3646 3366 20 0 1 0 10798280 237576192 1065 18446744073709551615 94174652813312 94174653706789 140724247111872 0 0 0 65536 3686404 1266761467 0 0 0 17 0 0 0 9 0 0 94174653946928 94174653994640 94174663303168 140724247119367 140724247119377 140724247119377 140724247121902 0" return fmt.Sprintf(template, comm, state) } testCases := []struct { content string res bool }{ {statString("bash", "R"), true}, // full string from actual system {statString("bash", "Z"), false}, // hostile process names that try to fake "I am dead" {statString("foo) Z ", "R"), true}, {statString("foo) Z", "R"), true}, {statString("foo)Z ", "R"), true}, {statString("foo)\nZ\n", "R"), true}, {statString("foo)\tZ\t", "R"), true}, {statString("foo) Z ", "R"), true}, // Actual stat string from https://github.com/rfjakob/zombiemem {"777295 (zombiemem) Z 773303 777295 773303 34817 777295 4227084 262246 0 1 0 18 49 0 0 20 0 2 0 8669053 0 0 18446744073709551615 0 0 0 0 0 0 0 0 0 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0", true}, } for _, tc := range testCases { statFile := mockProcdir + "/100/stat" if err := ioutil.WriteFile(statFile, []byte(tc.content), 0600); err != nil { t.Fatal(err) } if is_alive(100) != tc.res { t.Errorf("have=%v, want=%v for /proc/100/stat=%q", is_alive(100), tc.res, tc.content) } } } 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_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 Test_get_cmdline(t *testing.T) { pid := os.Getpid() res, comm := get_cmdline(pid) if res != 0 { t.Fatalf("error %d", res) } if len(comm) == 0 { t.Fatalf("empty process cmdline %q", comm) } t.Logf("process cmdline %q", comm) // Error case res, comm = get_cmdline(INT32_MAX) if res != -ENOENT { t.Fail() } if comm != "" { t.Fail() } } func Test_parse_proc_pid_stat_buf(t *testing.T) { should_error_out := []string{ "", "x", "\000\000\000", ")", } for _, v := range should_error_out { res, _ := parse_proc_pid_stat_buf(v) if res { t.Errorf("Should have errored out at %q", v) } } } func Test_parse_proc_pid_stat_1(t *testing.T) { stat, err := linuxproc.ReadProcessStat("/proc/1/stat") if err != nil { t.Fatal(err) } res, have := parse_proc_pid_stat(1) if !res { t.Fatal(res) } want := have want.state = _Ctype_char(stat.State[0]) want.ppid = _Ctype_int(stat.Ppid) want.num_threads = _Ctype_long(stat.NumThreads) want.rss = _Ctype_long(stat.Rss) if have != want { t.Errorf("\nhave=%#v\nwant=%#v", have, want) } } func Test_parse_proc_pid_stat_Mock(t *testing.T) { mockProcdir, err := ioutil.TempDir("", t.Name()) if err != nil { t.Fatal(err) } procdir_path(mockProcdir) defer procdir_path("/proc") if err := os.Mkdir(mockProcdir+"/100", 0700); err != nil { t.Fatal(err) } // Real /proc/pid/stat string for gnome-shell template := "549077 (%s) S 547891 549077 549077 0 -1 4194560 245592 104 342 5 108521 28953 0 1 20 0 23 0 4816953 5260238848 65528 18446744073709551615 94179647238144 94179647245825 140730757359824 0 0 0 0 16781312 17656 0 0 0 17 1 0 0 0 0 0 94179647252976 94179647254904 94179672109056 140730757367876 140730757367897 140730757367897 140730757369827 0" content := []string{ fmt.Sprintf(template, "gnome-shell"), fmt.Sprintf(template, ""), fmt.Sprintf(template, ": - )"), fmt.Sprintf(template, "()()()())))(((()))()()"), fmt.Sprintf(template, " \n\n "), } // Stupid hack to get a C.pid_stat_t _, want := parse_proc_pid_stat(1) want.state = 'S' want.ppid = 547891 want.num_threads = 23 want.rss = 65528 for _, c := range content { statFile := mockProcdir + "/100/stat" if err := ioutil.WriteFile(statFile, []byte(c), 0600); err != nil { t.Fatal(err) } res, have := parse_proc_pid_stat(100) if !res { t.Errorf("parse_proc_pid_stat returned %v", res) } if have != want { t.Errorf("/proc/100/stat=%q:\nhave=%#v\nwant=%#v", c, have, want) } } } func permute_is_larger(t *testing.T, sort_by_rss bool, procs []mockProcProcess) { args := poll_loop_args_t(sort_by_rss) for i := range procs { for j := range procs { // If the entry is later in the list, is_larger should return true. want := j > i have := is_larger(&args, procs[i], procs[j]) if want != have { t.Errorf("j%d/pid%d larger than i%d/pid%d? want=%v have=%v", j, procs[j].pid, i, procs[i].pid, want, have) } } } } func Test_is_larger(t *testing.T) { procs := []mockProcProcess{ // smallest {pid: 100, oom_score: 100, VmRSSkiB: 1234}, {pid: 101, oom_score: 100, VmRSSkiB: 1238}, {pid: 102, oom_score: 101, VmRSSkiB: 4}, {pid: 103, oom_score: 102, VmRSSkiB: 4}, {pid: 104, oom_score: 103, VmRSSkiB: 0, num_threads: 2}, // zombie main thread // largest } mockProc(t, procs) defer procdir_path("/proc") t.Logf("procdir_path=%q", procdir_path("")) permute_is_larger(t, false, procs) } func Test_is_larger_by_rss(t *testing.T) { procs := []mockProcProcess{ // smallest {pid: 100, oom_score: 100, VmRSSkiB: 4}, {pid: 101, oom_score: 100, VmRSSkiB: 8}, {pid: 102, oom_score: 101, VmRSSkiB: 8}, {pid: 103, oom_score: 99, VmRSSkiB: 12}, {pid: 104, oom_score: 102, VmRSSkiB: 0, num_threads: 2}, // zombie main thread {pid: 105, oom_score: 102, VmRSSkiB: 12}, // largest } mockProc(t, procs) defer procdir_path("/proc") t.Logf("procdir_path=%q", procdir_path("")) permute_is_larger(t, true, procs) } func Benchmark_parse_meminfo(b *testing.B) { enable_debug(false) for n := 0; n < b.N; n++ { parse_meminfo() } } func Benchmark_kill_process(b *testing.B) { enable_debug(false) for n := 0; n < b.N; n++ { kill_process() } } func Benchmark_find_largest_process(b *testing.B) { enable_debug(false) for n := 0; n < b.N; n++ { find_largest_process() } } func Benchmark_get_oom_score(b *testing.B) { enable_debug(false) pid := os.Getpid() for n := 0; n < b.N; n++ { get_oom_score(pid) } } func Benchmark_get_oom_score_adj(b *testing.B) { enable_debug(false) pid := os.Getpid() for n := 0; n < b.N; n++ { var out int get_oom_score_adj(pid, &out) } } func Benchmark_get_cmdline(b *testing.B) { enable_debug(false) pid := os.Getpid() for n := 0; n < b.N; n++ { res, comm := get_cmdline(pid) if len(comm) == 0 { b.Fatalf("empty process cmdline %q", comm) } if res != 0 { b.Fatalf("error %d", res) } } } func Benchmark_parse_proc_pid_stat(b *testing.B) { enable_debug(false) pid := os.Getpid() for n := 0; n < b.N; n++ { res, out := parse_proc_pid_stat(pid) if out.num_threads == 0 { b.Fatalf("no threads???") } if !res { b.Fatal("failed") } } }