pax_global_header00006660000000000000000000000064136216266760014531gustar00rootroot0000000000000052 comment=7921f0d878fd62669459d590fad0f70835995812 oomd-0.3.1/000077500000000000000000000000001362162667600124705ustar00rootroot00000000000000oomd-0.3.1/.clang-format000066400000000000000000000050311362162667600150420ustar00rootroot00000000000000--- AccessModifierOffset: -1 AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: true AlignOperands: false AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ForEachMacros: [ FOR_EACH, FOR_EACH_ENUMERATE, FOR_EACH_KV, FOR_EACH_R, FOR_EACH_RANGE, ] IncludeCategories: - Regex: '^<.*\.h(pp)?>' Priority: 1 - Regex: '^<.*' Priority: 2 - Regex: '.*' Priority: 3 IndentCaseLabels: true IndentWidth: 2 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: false PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never ... oomd-0.3.1/.gitignore000066400000000000000000000002641362162667600144620ustar00rootroot00000000000000.DS_Store /build/ /tags node_modules lib/core/metadata.js lib/core/MetadataBlog.js website/translated_docs website/build/ website/yarn.lock website/node_modules website/i18n/* oomd-0.3.1/.travis.yml000066400000000000000000000012201362162667600145740ustar00rootroot00000000000000dist: bionic addons: apt: packages: - pkg-config - libsystemd-dev - libjsoncpp-dev - googletest - meson - g++-8 language: cpp script: - meson build/ - ninja -C build/ - ninja -C build/ test matrix: include: - compiler: gcc env: - CC="gcc-8" CXX="g++-8" - compiler: clang - env: - JOB_TYPE="clang-format" addons: apt: packages: - clang-format script: - ./clang-format.sh - git diff --exit-code notifications: irc: channels: - "chat.freenode.net#oomd" on_success: change on_failure: always oomd-0.3.1/CODE_OF_CONDUCT.md000066400000000000000000000064341362162667600152760ustar00rootroot00000000000000# Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make 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 within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. 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 . 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 oomd-0.3.1/CONTRIBUTING.md000066400000000000000000000027551362162667600147320ustar00rootroot00000000000000# Contributing to oomd We want to make contributing to this project as easy and transparent as possible. ## Our Development Process We review pull requests internally (but also publically as applicable). Following a successful review, we commit internally and sync back to open source. ## Pull Requests We actively welcome your pull requests. 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, add tests. 3. If you've changed APIs, update the documentation. 4. Ensure the test suite passes. 5. Make sure your code lints. 6. If you haven't already, complete the Contributor License Agreement ("CLA"). ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. Complete your CLA here: ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. ## Coding Style * 2 spaces for indentation rather than tabs * 80 character line length ## License By contributing to oomd, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree. oomd-0.3.1/LICENSE000066400000000000000000000432301362162667600134770ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. oomd-0.3.1/README.md000066400000000000000000000067111362162667600137540ustar00rootroot00000000000000# oomd [![Build Status](https://travis-ci.com/facebookincubator/oomd.svg?branch=master)](https://travis-ci.com/facebookincubator/oomd) oomd is *userspace* Out-Of-Memory (OOM) killer for linux systems. ## Background Out of memory killing has historically happened inside kernel space. On a [memory overcommitted][0] linux system, malloc(2) and friends usually never fail. However, if an application dereferences the returned pointer and the system has run out of physical memory, the linux kernel is forced to take extreme measures, up to and including killing processes. This is sometimes a slow and painful process because the kernel can spend an unbounded amount of time swapping in and out pages and evicting the page cache. Furthermore, [configuring policy][1] is not very flexible while being somewhat complicated. oomd aims to solve this problem in userspace. oomd leverages PSI and cgroupv2 to monitor a system holistically. oomd then takes corrective action in userspace before an OOM occurs in kernel space. Corrective action is configured via a flexible plugin system, in which custom code can be written. By default, this involves killing offending processes. This enables an unparalleled level of flexibility where each workload can have custom protection rules. Furthermore, time spent livedlocked in kernelspace is minimized. In practice at Facebook, we've regularly seen 30 minute host lockups go away entirely. ## Installing from RPMs on Fedora RPMs are currently available for Fedora 30+ from a COPR repository. You can enable the COPR repository for oomd with: $ sudo dnf copr enable filbranden/oomd Then install oomd with: $ sudo dnf install oomd Finally, enable and start it with: $ sudo systemctl enable --now oomd.service ## Building from source Note that oomd requires PSI to function. This kernel feature has been merged into the 4.20 release. oomd currently depends on [meson][2] and [jsoncpp][4]. [libsystemd][6] is an optional dependency. oomd also requires GCC 8+ or clang 6+. Other compilers have not been tested. $ git clone https://github.com/facebookincubator/oomd $ cd oomd/oomd $ meson build && ninja -C build $ cd build && sudo ninja install ## Configuration See [docs/configuration.md](docs/configuration.md) for a high level overview and some examples. See [docs/core_plugins.md](docs/core_plugins.md) for a quick reference on core plugin capabilities. See [docs/production_setup.md](docs/production_setup.md) for guidelines on how oomd should be set up in a production environment. ## Running tests oomd depends on [gtest/gmock][5] to run tests. Installing gtest/gmock from master is preferred. If meson detects gtest/gmock is installed, meson will generate build rules for tests. $ cd oomd $ rm -rf build $ meson build && ninja test -C build ## Writing custom plugins It is both possible and encouraged to write custom plugins. The codebase is designed to make writing plugins as easy as possible. See [docs/writing_a_plugin.md](docs/writing_a_plugin.md) for a tutorial. ## Help / Discussion / Support Join our **#oomd** channel on irc.freenode.net! ## License oomd is GPL 2 licensed, as found in the LICENSE file. [0]: https://www.kernel.org/doc/Documentation/vm/overcommit-accounting [1]: https://lwn.net/Articles/317814/ [2]: http://mesonbuild.com/ [4]: https://github.com/open-source-parsers/jsoncpp [5]: https://github.com/google/googletest [6]: https://github.com/systemd/systemd/tree/master/src/libsystemd/ oomd-0.3.1/clang-format.sh000077500000000000000000000003271362162667600154030ustar00rootroot00000000000000#!/bin/bash set -euo pipefail if ! command -v clang-format &> /dev/null; then echo "Please install clang-format first" exit 1 fi find . \( -iname '*.[ch]' -o -iname '*.cpp' \) -exec clang-format -i {} \; oomd-0.3.1/docs/000077500000000000000000000000001362162667600134205ustar00rootroot00000000000000oomd-0.3.1/docs/auxiliary_plugins.md000066400000000000000000000041711362162667600175150ustar00rootroot00000000000000# Auxiliary plugins Auxiliary plugins are plugins that are either not generic enough to be be considered core or require optional dependencies. # Actions ## systemd_restart ### Arguments service post_action_delay=15 (optional) dry=false (optional) ### Description Restarts systemd service: `service`. STOP on success, CONTINUE otherwise. ## senpai ### Arguments cgroup limit_min_bytes=1<<30 (optional) limit_max_bytes=500<<30 (optional) interval=6 (optional) pressure_ms=10 (optional) pressure_ms=10 (optional) max_probe=0.01 (optional) max_backoff=1.0 (optional) coeff_probe=10 (optional) coeff_backoff=20 (optional) ### Description Slowly applies memory pressure to `cgroup` by decreasing memory.high until the cgroup begins to experience some memory pressure. Once memory pressure is detected, back off by increasing memory.high. Over time, `cgroup`'s memory.current will converge to the actual amount of memory it needs to operate. `cgroup` is the cgroup to act on. `cgroup` supports multi-cgroup and wildcarded paths. Eg: cgroup=workload.slice/workload-*.slice,system.slice The root host can be encoded as "/". `limit_min_bytes` is the minimum value that should be written to memory.high. `limit_max_bytes` is the maximum value that should be written to memory.high. `interval` is the number of event loop ticks to wait before adjusting memory.high. If memory pressure is experienced `interval` is ignored. `pressure_ms` is the target # of milliseconds per event loop tick that the target cgroup is under `some` memory pressure. `max_probe` is how aggressively to probe for `pressure_ms` memory pressure. `max_backoff` is how aggressively to back off after `pressure_ms` memory pressure is reached. `coeff_probe` determines when `max_probe` aggression is reached . A coefficient of 10 means the adjustment curve reaches the limit when pressure is 10x the target pressure. `coeff_backoff` determines when `max_backoff` back off is reached. A coefficient of 10 means the adjustment curve reaches the limit when pressure is 10x the target pressure. STOP on success, CONTINUE otherwise. oomd-0.3.1/docs/configuration.md000066400000000000000000000127711362162667600166210ustar00rootroot00000000000000# Configuration ## Design principles oomd is designed to be as flexible and as extensible as possible. To that end, oomd is configured via a declarative configuration file. The idea is you can have a set of memory protection rules that are orthogonal and intuitive to reason about. In a sense it's a lot like iptables chains work (but much better, I promise). ## Schema oomd configs have a loosely defined BNF: ARG: : NAME: PLUGIN: { "name": NAME, "args": { ARG[,ARG[,...]] } } DETECTOR: PLUGIN DETECTOR_GROUP: [ NAME, DETECTOR[,DETECTOR[,...]] ] ACTION: PLUGIN DROPIN: "disable-on-drop-in": , "detectors": , "actions": SILENCE_LOGS: "silence-logs": "NAME[,NAME[,...]]" RULESET: [ NAME, DROPIN, SILENCE_LOGS, "detectors": [ [DETECTOR_GROUP[,DETECTOR_GROUP[,...]]] ], "actions": [ [ACTION[,ACTION[,...]]] ], ] ROOT: { "rulesets": [ RULESET[,RULESET[,...]] ] } In plain english, the general idea is that each oomd config one or more RULESETs. Each RULESET has a set of DETECTOR_GROUPs and a set of ACTIONs. Each DETECTOR_GROUP has a set of DETECTORs. Both DETECTORs and ACTIONs are PLUGIN types. That means _everything_ is a plugin in oomd. The rules on how a conforming config is evaluated at runtime are described in the next section. ### Notes * For `SILENCE_LOGS`, the currently supported log entities are * `engine`: oomd engine logs * `plugins`: logs written by plugins ## Runtime evaluation rules * Every plugin must return CONTINUE or STOP. * CONTINUE * For DETECTORs, noop chain * For ACTIONs, continue executing the current ACTION chain * STOP * For DETECTORs, evaluate the current DETECTOR_GROUP chain to false * For ACTIONs, abort execution of the current ACTION chain * DETECTOR_GROUPs evaluate true if and only if all DETECTORs in the chain return CONTINUE * For each RULESET, if _any_ DETECTOR_GROUP fires, the associated ACTION chain will begin execution ### Notes * For each event loop tick, all DETECTORs and DETECTOR_GROUPs will be run. This is to allow any detectors implementing sliding windows, if any, to update their windows ## Example This example uses the JSON front end. At time of writing (11/20/18), JSON is the only supported config front end. The config compiler has been designed with extensibility in mind as well. It would not be difficult to add another config front end. { "rulesets": [ { "name": "memory pressure protection", "detectors": [ [ "workload is under pressure and system is under a lot of pressure", { "name": "pressure_rising_beyond", "args": { "cgroup": "workload.slice", "resource": "memory", "threshold": "5", "duration": "15" } }, { "name": "pressure_rising_beyond", "args": { "cgroup": "system.slice", "resource": "memory", "threshold": "40", "duration": "15" } } ], [ "system is under a lot of pressure", { "name": "pressure_rising_beyond", "args": { "cgroup": "system.slice", "resource": "memory", "threshold": "80", "duration": "30" } } ] ], "actions": [ { "name": "kill_by_memory_size_or_growth", "args": { "cgroup": "system.slice/*" } } ] }, { "name": "low swap protection", "detectors": [ [ "swap is running low", { "name": "swap_free", "args": { "threshold_pct": "15" } } ] ], "actions": [ { "name": "kill_by_swap_usage", "args": { "cgroup": "system.slice/*,workload.slice/workload-wdb.slice/*,workload.slice/workload-tw.slice/*" } } ] } ] } This config, in english, says the following: * If the workload is under a memory pressure AND the system is under a moderate amount of pressure, kill a memory hog in the system * If the systems is under a lot of memory pressure, kill a memory hog in the system * If the system is running low on swap (this can cause pathological conditions), kill the cgroup using the most swap across the system and workloads. oomd-0.3.1/docs/core_plugins.md000066400000000000000000000202251362162667600164340ustar00rootroot00000000000000# Core plugins Note that this document is organized in two sections: detectors and actions. In theory, there's nothing stopping you from using a detector as an action and an action as a detector. The oomd runtime is completely agnostic. However, it would probably not be very useful in many cases. # Detectors ## pressure_rising_beyond ### Arguments cgroup resource threshold duration fast_fall_ratio=0.85 (optional) cgroup_fs=/sys/fs/cgroup (optional) ### Description `cgroup` specifies the "parent" cgroup(s) to monitor. Eg. if cgroup=system.slice, we would be monitoring everything inside system.slice. `cgroup` supports multi-cgroup and wildcard paths. Eg: cgroup=workload.slice/workload-*.slice,system.slice Note that extra spaces are not permitted betwen ','s. The root host can be encoded as "/". `resource` is io|memory CONTINUE if 1m pressure > `threshold` for longer than `duration` && trending above threshold (10s > `threshold`) && 10s not falling rapidly. STOP otherwise. ## memory_above ### Arguments cgroup threshold (optional) threshold_anon (optional) duration ### Description `cgroup` has the same semantics and features as `pressure_rising_beyond`. `threshold` and `threshold_anon` take either an absolute memory amount or a percentage of total memory used. Either one of these parameters must be specified. When both are specified, only `threshold_anon` is effective. An absolute memory amount threshold accepts combinations of K|M|G|T suffixed components. For example, `1.5M 32K 512` is interpreted as `1.5 * 2^20 + 32 * 2^10 + 512` bytes. NOTE: FOR BACKWARD COMPATIBILITY, A BARE NUMBER IS INTERPRETED AS MEGABYTES. A percentage threshold must be in the format `N%`, where `0 <= N <= 100`. If `threshold` is specified, CONTINUE if 10s total memory usage > `threshold` longer than `duration`, STOP otherwise. If `threshold_anon` is specified, CONTINUE if 10s anonymous memory usage > `threshold_anon` longer than `duration`, STOP otherwise. ## pressure_above ### Arguments cgroup resource threshold duration cgroup_fs=/sys/fs/cgroup (optional) ### Description `cgroup` has the same semantics and features as `pressure_rising_beyond`. `resource` is io|memory CONTINUE if 10s pressure > `threshold` longer than `duration` STOP otherwise. ## memory_reclaim ### Arguments cgroup duration ### Description `cgroup` has the same semantics and features as `pressure_rising_beyond`. CONTINUE if `cgroup`'s memory has been reclaimed in the past `duration` period. STOP otherwise. ## swap_free ### Arguments threshold_pct ### Description CONTINUE if percentage of free:total swap drops below `threshold_pct` %. STOP otherwise. ## exists ### Arguments cgroup negate=false (optional) ### Description `cgroup` supports comma separated arguments and wildcards. When `negate` is `false`, if `cgroup` exists, CONTINUE. STOP otherwise. When `negate` is `true`, if `cgroup` doesn't exist, CONTINUE. STOP otherwise. ## nr_dying_descendants ### Arguments cgroup count lte=true (optional) negate=false (optional) ### Description `cgroup` supports comma separated arguments and wildcards. The plugin triggers if any of the globbed cgroups matches the condition. When `lte` is `true`, if `nr_dying_descendants(cgroup) <= count`, CONTINUE. STOP otherwise. When `lte` is `false`, if `nr_dying_descendants(cgroup) > count`, CONTINUE. STOP otherwise. ## dump_cgroup_overview ### Arguments cgroup always=false ### Description `cgroup` has the same semantics and features as `pressure_rising_beyond`. However, this detector cannot monitor the root host. Dumps the system overview for `cgroup` to stderr if memory pressure is non-negligible. If `always` is set to `true`, then cgroup overviews will always be printed. Always returns CONTINUE. ## adjust_cgroup ### Arguments cgroup memory memory_scale=1 (optional) ### Description `cgroup` has the same format and features as `pressure_rising_beyond` but specifies the target cgroups directly rather than the parents. `memory_scale` is the scale factor to be multipled to memory usage of the cgroup. It can be zero or a positive floating point number. For example, `1.5` makes the cgroup's memory usage to be scaled up by 50%. `memory` is the number of bytes to be added to memory usage of the cgroup. It can be negative, 0 or positive. It accepts combinations of K|M|G|T suffixed components. For example, `1.5M 32K 512` is interpreted as 1.5 * 2^20 + 32 * 2^10 + 512 bytes. When both `memory_scale` and `memory` are specified, `memory_scale` is applied first. Adjustments are applied to the shared context at the time when the plugin is invoked and can be updated by later invocations. Always returns CONTINUE. # Actions ## kill_by_memory_size_or_growth ### Arguments cgroup size_threshold=50 (optional) min_growth_ratio=1.25 (optional) growing_size_percentile=80 (optional) post_action_delay=15 (optional) cgroup_fs=/sys/fs/cgroup (optional) dry=false (optional) ### Description `cgroup` specifies the cgroup(s) that should be considered for killing. Eg. if cgroup=system.slice/*, everything inside system.slice would be considered for killing. `cgroup` also support multi-cgroup and wildcard paths. Eg. cgroup=workload.slice/workload-*.slice/*,system.slice/* Note that extra spaces are not permitted betwen ','s. Kill the biggest (memory.current - memory.low) child cgroup if larger than `size_threshold` percent or kill the fastest growing over `min_growth_ratio` of the biggest `growing_size_percentile` by size. True if killed something, which terminates actions. Sleeps for `post_action_delay` if a kill was performed. If `dry` is set to true, does not actually perform kill but prints via logs what kill it would have done. Avoids killing cgroups which aren't experiencing memory pressure at all as they aren't contributing the pressure anyway. cgroups that are killed have the "trusted.oomd_kill" xattr set to the number of SIGKILLs sent to resident processes. STOP if killed something (even if dry=true). CONTINUE otherwise. ## kill_by_swap_usage ### Arguments cgroup threshold=1 (optional) post_action_delay=15 (optional) dry=false (optional) ### Description `cgroup` follows the same semantics and options as `kill_by_memory_size_or_growth`. `threshold` follows the same semantics and options as `memory_above`. Sleeps for `post_action_delay` following a kill. `dry` follows the same semantics and options as `kill_by_memory_size_or_growth` Note that there is no `cgroup_fs` option. This plugin does not inspect cgroup states beyond what the core runtime provides. The core runtime knows about cgroup_fs location via the cmd line args. Kills the child with the largest swap usage. cgroups that are killed have the "trusted.oomd_kill" xattr set to the number of SIGKILLs sent to resident processes. STOP if killed something (even if dry=true), CONTINUE otherwise. ## kill_by_pressure ### Arguments cgroup resource post_action_delay=15 (optional) cgroup_fs=/sys/fs/cgroup (optional) dry=false (optional) ### Description `cgroup` follows the same semantics and options as `kill_by_memory_size_or_growth`. `resource` is io|memory Sleeps for `post_action_delay` following a kill. `dry` follows the same semantics and options as `kill_by_memory_size_or_growth` Kills the child generating the most pressure. cgroups that are killed have the "trusted.oomd_kill" xattr set to the number of SIGKILLs sent to resident processes. STOP if killed something (even if dry=true), CONTINUE otherwise. ## kill_by_io_cost ### Arguments cgroup post_action_delay=15 (optional) cgroup_fs=/sys/fs/cgroup (optional) dry=false (optional) ### Description `cgroup` follows the same semantics and options as `kill_by_memory_size_or_growth`. Sleeps for `post_action_delay` following a kill. `dry` follows the same semantics and options as `kill_by_memory_size_or_growth` Kills the child generating the most io cost. cgroups that are killed have the "trusted.oomd_kill" xattr set to the number of SIGKILLs sent to resident processes. STOP if killed something (even if dry=true), CONTINUE otherwise. oomd-0.3.1/docs/drop_in_configs.md000066400000000000000000000112741362162667600171110ustar00rootroot00000000000000# Drop-in configs Drop-in configs are a way to modify oomd's rule engine at runtime. For details on regular (non-drop-in) configuration, please see [configuration.md](configuration.md). ## Background Drop-in configs are useful in environments hosting container or container-like environments. For example, consider the following cgroup hierarchy: ``` / ├── system.slice │   └── chef.service └── workload.slice └── workload-container ├── sidecar └── task ├── nested-containers │   ├── container1 │   ├── container2 │   └── container3 └── nested-containers-sidecar ``` oomd configs will usually set `task` as the kill target. This is correct and optimal most of the time. However, in cases where containers are nested, eg. when a container hosts its own container-like environment, this becomes suboptimal. In our example, `task` holds its own set of nested containers, `container1`, `container2`, and `container3`. With the standard oomd config, all three nested containers will be killed if any of the three OOM. What would be nice is if `task` could tell oomd to kill one of the three nested containers. Then also if the container is migrated off the host, the modification is removed. This is what drop-in configs accomplish. ## Configuration ### Base config You must make base config modifications to enable drop-in configs. `disable-on-drop-in` disables execution of the targeted base ruleset if a drop-in config is added. `detectors` and `actions` enable drop-in configs for detector groups and the action chain, respectively. By default all options are off. ### Drop-in config Drop-in configs use the exact same format as base configs with the following caveat: * Both detector groups and actions may be omitted ## Usage Run oomd with `--drop-in-dir DIR` to have oomd monitor `DIR` for drop-in configs. Whenever a file is modified-in or moved-into `DIR`, oomd will try to parse the file as an oomd config. If it parses and compiles, oomd will try to inject the drop-in config into the engine. Filenames beginning with '.' are ignored. ### Mechanism * Every drop-in config must target a ruleset in the base config * Targeting is done with the ruleset `name` * If there is more than one match, the first base ruleset will be chosen * If more than one drop-in config is present, they are added last-in-first-out (LIFO) order * A single drop-in config may contain more than one ruleset * Drop-in configs override the targeted ruleset on detector group or action group granularity * Behind the scenes, oomd will clone a copy of the targeted ruleset and replace the dropped-in detector groups and action groups. That new ruleset will be run before the targeted ruleset. ## Example ### Base config ``` { "rulesets": [ { "name": "user session protection", "drop-in": { "detectors": true, "actions": true, "disable-on-drop-in": true }, "detectors": [ [ "user pressure above 60 for 30s", { "name": "dump_cgroup_overview", "args": { "cgroup": "user.slice,workload.slice,www.slice" } }, { "name": "pressure_above", "args": { "cgroup": "user.slice,workload.slice,www.slice", "resource": "memory", "threshold": "60", "duration": "30" } }, { "name": "memory_reclaim", "args": { "cgroup": "user.slice,workload.slice,www.slice", "duration": "10" } } ] ], "actions": [ { "name": "continue" } ] } ] } ``` ### Drop-in config ``` { "rulesets": [ { "name": "user session protection", "detectors": [ [ "system pressure above 80 for 60s", { "name": "dump_cgroup_overview", "args": { "cgroup": "system.slice", "always": true } } ] ] } ] } ``` oomd-0.3.1/docs/io_cost.md000066400000000000000000000057711362162667600154130ustar00rootroot00000000000000# IO Cost IO cost is one of the many metrics (swap, memory, pressure, etc) logged by oomd periodically, based on which the plugins can decide if an alarm should be triggered, or which cgroup should be cleaned up. This documentation provides some background about IO cost in the context of oomd. ## What is IO Cost IO cost is a unit-less metric measuring how much load is put on some IO devices by a cgroup. It is based on a simple model using bandwidth and iops of read, write, and trim to approximate some definition of load. In order to calculate IO cost, oomd reads the [`io.stat`](https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html#interface-files) file in each cgroup, from where the cumulative IOs and bytes of read/write/trim are obtained. Then the difference between two consecutive intervals divided by the duration is bandwidth and iops. The dot-product between some coefficients and the measurements is the IO cost. By default, these are the coefficients for HDD and SSD devices: ``` static const struct Oomd::IOCostCoeffs default_hdd_coeffs = { .read_iops = 1.31e-3, .readbw = 1.13e-7, .write_iops = 2.58e-1, .writebw = 5.04e-7, .trim_iops = 0, .trimbw = 0, }; static const struct Oomd::IOCostCoeffs default_ssd_coeffs = { .read_iops = 1.21e-2, .readbw = 6.25e-7, .write_iops = 1.07e-3, .writebw = 2.61e-7, .trim_iops = 2.37e-2, .trimbw = 9.10e-10, }; ``` These number are obtained through experiments with disks running on Facebook servers. They may not match other IO devices and therefore running experiments with your own devices is recommended. ## How to Configure IO Cost IO cost in oomd is configured by command line arguments: ``` --device DEVS --hdd-coeffs COEFFS --ssd-coeffs COEFFS ``` ### `--device DEVS` This option tells oomd what are the root devices, or ones that will contribute to calculating the IO cost. The `io.stat` file can have multiple lines, one for each IO device that the cgroup has interacted with. Only lines belong to the root devices will be used to calculated the IO cost. This option expects `DEVS` in the format of comma separated `:` pairs of devices, e.g. `252:1,253:1` for two devices, one with major=252 and minor=1 and the other with major=253 and minor=1. This will tell oomd to calculate IO cost by summing the bandwitdh and iops data of both `252:1` and `253:1`. ### `--hdd-coeffs COEFFS` This option specifies an alternative to the `default_hdd_coeffs` shown above. It expects a comma separated list of numeric values, which will be coefficients in the order of read iops, read bandwidth, write iops, write bandwidth, trim iops, and trim bandwidth. See `std::stod` for support numeric formats. If less than 6 values are passed, remaining ones are set zero. Coefficients for trim are likely ignored and should not be passed. ### `--ssd-coeffs COEFFS` This option specifies an alternative to the `default_ssd_coeffs` shown above. It has the same format as `--hdd-coeffs` but coefficients for trim should be included. oomd-0.3.1/docs/production_setup.md000066400000000000000000000057521362162667600173610ustar00rootroot00000000000000# Production setup The document covers how oomd is set up in production at Facebook. ## Host setup ### kernel command line The follow command line arguments must be set: * `swapaccount=1` * or equivalent, if the kernel has this turned on via compile time flags * `systemd.unified_cgroup_hierarchy=1` * or equivalent, if systemd has this turned on via compile time flags ### cgroup2 The host must be running unified cgroup (cgroup2) alone. oomd is not designed to support mixed (legacy and unified) hierarchies. oomd expects the cgroup2 filesystem to be mounted at `/sys/fs/cgroup` but the default can be changed with `--cgroup-fs`. ### systemd The host must be managed by systemd. Furthermore, resource accounting must be turned on for all units monitored by oomd. The easiest way to turn on resource accounting is by changing the system defaults: ``` DefaultCPUAccounting=true DefaultIOAccounting=true DefaultMemoryAccounting=true DefaultTasksAccounting=true ``` Refer to https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html#DefaultCPUAccounting= for more details. ### kernel w/ PSI (pressure stall information) oomd requires PSI to function. Kernels 4.20 and above should have PSI. Verify your kernel has been compiled with PSI by running: ``` $ zcat /proc/config.gz | grep CONFIG_PSI CONFIG_PSI=y ``` ### swap The system must have swap enabled for oomd to function correctly. With swap enabled, the system spends enough time swapping pages to let oomd react. Without swap, the system enters a livelocked state much more quickly and may prevent oomd from doing in a reasonable amount of time. While this kind of sounds like a crutch, swap is generally very good on modern systems. See https://chrisdown.name/2018/01/02/in-defence-of-swap.html for more details on swap. The current recommendation for swap size is at least 1x size of physical memory. ## Service setup oomd must be run in a protected cgroup. In other words, we use a specialized systemd service setup to guarantee oomd resources so that oomd can act in resource starved scenarios. You typically group host critical services in their own special cgroup. Perhaps named `hostcritical.slice`. oomd should be grouped in here with other host critical services like `sshd.service`. `hostcritical.slice` should be guaranteed a minimum amount of reserved memory via `memory.min`. This essentially `mlockall`s oomd except the kernel sets aside the memory ahead of time. A portion of that memory - 64M should be enough - should be given to oomd. The resulting config should look something like: ``` $ systemctl cat oomd.service | grep Memory MemoryMin=64M MemoryLow=64M $ systemctl show fb-oomd.service | grep ControlGroup ControlGroup=/hostcritical.slice/oomd.service ``` TODO: document io.latency config ## Monitoring Stats are currently collected by grep'ing through oomd logs files. This is obviously very brittle but work is underway to provide a structured stats interface. TODO: document the WIP structured stats collection interface oomd-0.3.1/docs/stats.md000066400000000000000000000026411362162667600151030ustar00rootroot00000000000000# Structured stats collection The structured stats collection is a threadsafe key-value store, with `std::string` for keys and `int` for values. It can be also be queried from a different process using the StatsClient class. ## Internal API ### Get stats `std::unordered_map getStats()` Returns an unordered map copy of the current stats. ### Increment a key-value pair in stats `int setStats(const std::string& key, int val)` This increments the value of the corresponding key by val. Returns 0 upon success, and 1 on error. ### Set a key-value pair in stats `int setStats(const std::string& key, int val)` Sets the corresponding key in stats to val. Returns 0 upon success, and 1 on error. ### Reset stats `int Oomd::resetStats()` Sets the value of all existing key-value pairs in the stats collection to 0. Returns 0 upon success, and 1 on error. ## External interface Command line flags: ##### `-s [SOCKET_PATH]` If a custom socket path was chosen for the stats collection, include it here. ##### `-d` Dumps all accumulated stats to stdout in a JSON string. $ /path/to/binary -d { "oomd.kills_structured" : 1, "oomd.restarts_structured" : 2 } $ /path/to/binary -d | jq '.["oomd.kills_structured"]' 1 #####`-r` Reset stats by setting all values to 0. Notes: If both `-d` and `-r` are included, `-d` is completed first. oomd-0.3.1/docs/writing_a_plugin.md000066400000000000000000000151271362162667600173110ustar00rootroot00000000000000# Writing a plugin Plugins are at the core of oomd. Everything that implements business logic must be done in a plugin. If you haven't already, you should read [configuration.md](configuration.md). That doc explains the high level goals of plugins. ## Interface The `BasePlugin` interface is found in `engine/BasePlugin.h`. `BasePlugin` is a pure virtual class that defines what is expected of each plugin. This document assumes you have already read through `engine/BasePlugin.h`. If you have not, please do. Ignoring the comments and less relevant bits, every plugin must implement the following two methods: virtual int init( MonitoredResources& resources, const PluginArgs& args) = 0; /* where PluginArgs is an alias of std::unordered_map */ virtual PluginRet run(OomdContext& context) = 0; ### init(..) The `init(..)` method is called by the config compiler. The config compiler transforms (typically) JSON configuration into actual data structures oomd will work with. As part of the compilation process, oomd will run `init(..)` on every instantiated plugin. `MonitoredResources& resources` is a typedef'd std::unordered_set of strings. Plugins should insert any cgroups they want accounting on into `resources`. Accounting information will be passed back to the plugin at each event loop tick in `run(..)` via `OomdContext& context`. Kill plugins will typically place any cgroups they are instructed to possibly kill into `resources`. `const PluginArgs& args` is a map of arguments that are provided to the plugin. Each plugin, as defined by the config schema, is allowed to have a JSON object containing string->string key/value pairs representing the configuration. If plugin initialization is success, the plugin must return zero. A non-zero return value will fail the config compilation process (and usually exit the process). Plugins are encouraged to print a useful error message before returning non-zero. ### run(..) `run(..)` is called by the core oomd runtime each event loop tick. The duration between each tick can be configured via `--interval,-i` on the command line. `run(..)` is the work horse function of every plugin. This is where most, if not all, of the work is expected to be done. You can do pretty much whatever you want in the plugin. Make syscalls, inspect the file system, mess with other plugins by modifying `OomdContext`, name it. (Not to imply doing all of those things are a productive idea). `OomdContext& context` is an object that contains state about the system. `OomdContext&` typically contains accounting information on every cgroup that the core oomd runtime has been instructed to monitor. This means that cgroups other plugins placed into `resources` in `init(..)` will also be available. ## Plugin registration You might have wondered, how does the config compiler know which plugin name maps to which C++ class? This section goes into the details of plugin registration. oomd employs a static plugin registration system. In other words, oomd plugins will insert themselves into a map of plugin name -> plugin factory method. The details of static registration are out of the scope of this document, but plenty of sources exist online that explain the details. In short, static registration ensures that the map of X -> Y will be fully populated before the program reaches `int main()`. Plugins are required to register themselves to the plugin registry via the `REGISTER_PLUGIN` macro defined in `oomd/PluginRegistry.h`. If you do not register your plugin and try to use it in a config, the compilation process will fail and oomd will not start up. Note that `REGISTER_PLUGIN` must be a static factory method that returns a pointer to an instance of your plugin allocated on the heap. There is not currently support to register custom deleter functions. This means you may not use custom allocators (by overloading `new`/`delete` or otherwise) to create instances of your plugin. ## Logging Plugins are encouraged to use the oomd logging facilities. `OLOG` is an ostream style macro that prints logs asynchronously. This is useful as systems under intense memory pressure are not usually able to write to filesystems or output things to stdout/sterr. It's usually not a good idea to log inline on a production host, as writes can block indefinitely, thus limiting the utility of oomd. `OLOG` is also smart enough to log inline (ie not async) when run in unit tests. Logging async in unit tests can mess with gtest output parsing. ## Anatomy of ContinuePlugin There is a functioning (but useless) example plugin included in oomd's set of core plugins: ContinuePlugin. You can find the code at `plugins/ContinuePlugin.h` and `plugins/ContinuePlugin.cpp.` ### ContinuePlugin.h #pragma once #include "oomd/engine/BasePlugin.h" namespace Oomd { Plugins do not need to exist in the `Oomd` namespace but it'll save you some typing. class ContinuePlugin : public Engine::BasePlugin { All plugins must derive from `BasePlugin`, as discussed in the previous section. public: int init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& /* unused */) override { return 0; } The `init(..)` method is implemented inline here. Note that we do not examine our arguments or register any resource requirements. We return 0 to signify success. Engine::PluginRet run(OomdContext& /* unused */) override { return Engine::PluginRet::CONTINUE; } Our plugin does nothing besides return CONTINUE. static ContinuePlugin* create() { return new ContinuePlugin(); } } // namespace Oomd This is our static factory method. Note it has to be static, as the config compiler uses the static plugin registry, and all factory functions in the plugin registry must be static. ~ContinuePlugin() = default; We can use the default destructor. ### ContinuePlugin.cpp #include "oomd/plugins/ContinuePlugin.h" #include "oomd/PluginRegistry.h" namespace Oomd { REGISTER_PLUGIN(continue, ContinuePlugin::create); } // namespace Oomd The only thing our cpp file does is register our plugin into the plugin registry. Carefully note that we did not register our plugin in the header file. Doing that might work for now, but will cause (somewhat cryptic) errors if someone decides to include your plugin header and subclass some things. What happens in this bad case is that there will be multiple places where your plugin will be registered. The `REGISTER_PLUGIN` macro is designed such that collision will occur if you try to register > 1 plugin with the same name. oomd-0.3.1/man/000077500000000000000000000000001362162667600132435ustar00rootroot00000000000000oomd-0.3.1/man/oomd.1000066400000000000000000000040161362162667600142640ustar00rootroot00000000000000.TH "oomd" "1" .SH NAME oomd \- Userspace Out-Of-Memory (OOM) killer for Linux systems .SH SYNOPSIS .B oomd .RI [ OPTION "] ..." .SH DESCRIPTION .B oomd leverages PSI and cgroupv2 to monitor a system holistically. .B oomd then takes corrective action in userspace before an OOM occurs in kernel space. Corrective action is configured via a flexible plugin system, in which custom code can be written. By default, this involves killing offending processes. This enables an unparalleled level of flexibility where each workload can have custom protection rules. Furthermore, time spent livedlocked in kernelspace is minimized. .SH OPTIONS .TP .B \-\-help, \-h Show this help message and exit .TP .B \-\-version, \-v Print version and exit .TP .BI "\-\-config, \-C " CONFIG Config file (default: \fI/etc/oomd/oomd.json\fR) .TP .BI "\-\-interval, \-i " INTERVAL Event loop polling interval (default: 5) .TP .BI "\-\-cgroup\-fs, \-f " FS Cgroup2 filesystem mount point (default: \fI/sys/fs/cgroup\fR) .TP .BI "\-\-check\-config, \-c " CONFIG Check config file (default: \fI/etc/oomd/oomd.json\fR) .TP .B \-\-list\-plugins, \-l List all available plugins .TP .BI "\-\-drop\-in\-dir, \-w " DIR Directory to watch for drop in configs .TP .BI "\-\-socket\-path, \-s " PATH Specify stats socket path (default: \fI/run/oomd/oomd\-stats.socket\fR) .TP .B \-\-dump\-stats, \-d Dump accumulated stats .TP .B \-\-reset\-stats, \-r Reset stats collection .TP .BI "\-\-device " DEVS Comma separated <\fImajor\fR>:<\fIminor\fR> pairs for IO cost calculation (default: none) .TP .BI "\-\-ssd\-coeffs " COEFFS Comma separated values for SSD IO cost calculation (default: see doc) .TP .BI "\-\-hdd\-coeffs " COEFFS Comma separated values for HDD IO cost calculation (default: see doc) .SH SEE ALSO https://github.com/facebookincubator/oomd#configuration .SH AUTHOR .B oomd is written by .UR https://opensource.fb.com/ Facebook, Inc .UE . This manual page was written by .MT mmyangfl@\:gmail.com Yangfl .ME for the Debian Project (and may be used by others). oomd-0.3.1/meson.build000066400000000000000000000126461362162667600146430ustar00rootroot00000000000000project('oomd', 'cpp', version : 'v0.3.1', meson_version : '>= 0.45', license : 'GPL2', default_options : ['stdsplit=false', 'cpp_std=c++17']) cpp_args = ['-DMESON_BUILD'] inc = include_directories('src') # Plumbing to #define a GIT_VERSION so Main.cpp can print it out vcs_tagger = [meson.source_root() + '/vcs_tagger.sh', meson.source_root(), meson.project_version()] version_h = vcs_tag( input : 'src/oomd/include/Version.h.in', output : 'Version.h', command : vcs_tagger) versiondep = declare_dependency(sources : version_h) srcs = files(''' src/oomd/Log.cpp src/oomd/Oomd.cpp src/oomd/OomdContext.cpp src/oomd/Stats.cpp src/oomd/StatsClient.cpp src/oomd/PluginRegistry.cpp src/oomd/config/ConfigCompiler.cpp src/oomd/config/ConfigTypes.cpp src/oomd/config/JsonConfigParser.cpp src/oomd/engine/DetectorGroup.cpp src/oomd/engine/Engine.cpp src/oomd/engine/Ruleset.cpp src/oomd/include/Assert.cpp src/oomd/include/CgroupPath.cpp src/oomd/plugins/AdjustCgroup.cpp src/oomd/plugins/BaseKillPlugin.cpp src/oomd/plugins/ContinuePlugin.cpp src/oomd/plugins/DumpCgroupOverview.cpp src/oomd/util/Fs.cpp src/oomd/util/Util.cpp src/oomd/plugins/MemoryAbove.cpp src/oomd/plugins/MemoryReclaim.cpp src/oomd/plugins/NrDyingDescendants.cpp src/oomd/plugins/PressureAbove.cpp src/oomd/plugins/PressureRisingBeyond.cpp src/oomd/plugins/SwapFree.cpp src/oomd/plugins/Senpai.cpp src/oomd/plugins/Exists.cpp src/oomd/plugins/KillIOCost.cpp src/oomd/plugins/KillMemoryGrowth.cpp src/oomd/plugins/KillSwapUsage.cpp src/oomd/plugins/KillPressure.cpp '''.split()) fixture_srcs = files(''' src/oomd/fixtures/FsFixture.cpp src/oomd/util/Fixture.cpp '''.split()) deps = [versiondep, dependency('jsoncpp'), dependency('threads')] # Optional dependencies systemd_dep = dependency('libsystemd', required: false) if systemd_dep.found() srcs += files(''' src/oomd/plugins/systemd/BaseSystemdPlugin.cpp src/oomd/plugins/systemd/SystemdRestart.cpp '''.split()) deps += [systemd_dep] endif oomd_lib = static_library('oomd', srcs, include_directories : inc, cpp_args : cpp_args, install : false, dependencies : deps) oomd_fixture_lib = static_library('oomd_fixture', fixture_srcs, include_directories : inc, cpp_args : cpp_args, dependencies : deps, link_with : oomd_lib) executable('oomd', files('src/oomd/Main.cpp'), include_directories : inc, cpp_args : cpp_args, dependencies : deps, install: true, link_whole : oomd_lib) prefixdir = get_option('prefix') bindir = join_paths(prefixdir, get_option('bindir')) sysconfdir = join_paths(prefixdir, get_option('sysconfdir')) oomdconfdir = join_paths(sysconfdir, 'oomd') systemunitdir = join_paths(prefixdir, 'lib/systemd/system') substs = configuration_data() substs.set('bindir', bindir) substs.set('sysconfdir', sysconfdir) substs.set('oomdconfdir', oomdconfdir) substs.set('systemunitdir', systemunitdir) configure_file( input : 'src/oomd/etc/oomd.service.in', output : 'oomd.service', configuration : substs, install_dir : systemunitdir) configure_file( input : 'src/oomd/etc/desktop.json', output : 'oomd.json', configuration : substs, install_dir : oomdconfdir) install_man('man/oomd.1') # Core tests # TODO: Normally we'd use a dictionary here, but they are only supported as of # Meson version 0.47.0. Ubuntu 18.04 provides only Meson version 0.45.1. core_tests = [ ['config', files('src/oomd/config/JsonConfigParserTest.cpp')], ['oomd', files('src/oomd/OomdTest.cpp')], ['util', files('src/oomd/util/FixtureTest.cpp', 'src/oomd/util/FsTest.cpp', 'src/oomd/util/ScopeGuardTest.cpp', 'src/oomd/util/UtilTest.cpp')], ['context', files('src/oomd/OomdContextTest.cpp')], ['log', files('src/oomd/LogTest.cpp')], ['assert', files('src/oomd/include/AssertTest.cpp')], ['cpath', files('src/oomd/include/CgroupPathTest.cpp')], ['compiler', files('src/oomd/config/ConfigCompilerTest.cpp')], ['plugin', files('src/oomd/plugins/CorePluginsTest.cpp')], ['stats', files('src/oomd/StatsTest.cpp')], ] # Optional tests if systemd_dep.found() core_tests += [['systemd_plugin', files('src/oomd/plugins/systemd/SystemdPluginsTest.cpp')]] endif gtest_dep = dependency('gtest', main : true, required : false) gmock_dep = dependency('gmock', required : false) if gtest_dep.found() and gmock_dep.found() deps += [gtest_dep, gmock_dep] foreach test_source_tuple : core_tests test_name = test_source_tuple[0] sources = test_source_tuple[1] executable_name_suffix = test_name + '_tests' test_executable = executable('oomd_' + executable_name_suffix, sources, include_directories : inc, cpp_args : cpp_args, dependencies : deps, link_whole : [oomd_lib, oomd_fixture_lib]) test(executable_name_suffix, test_executable, workdir : meson.source_root() + '/src') endforeach endif oomd-0.3.1/src/000077500000000000000000000000001362162667600132575ustar00rootroot00000000000000oomd-0.3.1/src/oomd/000077500000000000000000000000001362162667600142155ustar00rootroot00000000000000oomd-0.3.1/src/oomd/Log.cpp000066400000000000000000000112511362162667600154420ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/Log.h" #include #include #include #include #include #include #include #include #include #include "oomd/util/Util.h" namespace Oomd { LogStream::LogStream() : sink_(Log::get()) {} LogStream::LogStream(LogBase& sink) : sink_(sink) {} bool& LogStream::enabled() { static thread_local bool enabled = true; return enabled; } LogStream::~LogStream() { if (!enabled() || skip_) { return; } stream_ << std::endl; sink_.debugLog(stream_.str()); } Log::Log(int kmsg_fd, std::ostream& debug_sink, bool inl) : kmsg_fd_(kmsg_fd), inline_(inl) { // Start async debug log flushing thread if we are not inline logging if (!inline_) { io_thread_ = std::thread([this, &debug_sink] { this->ioThread(debug_sink); }); } } Log::~Log() { if (kmsg_fd_ >= 0) { ::close(kmsg_fd_); } { std::lock_guard lock(state_.lock); state_.ioThreadRunning = false; } state_.cv.notify_all(); if (io_thread_.joinable()) { io_thread_.join(); } } bool Log::init() { int kmsg_fd = ::open("/dev/kmsg", O_WRONLY); if (kmsg_fd < 0) { perror("open"); std::cerr << "Unable to open outfile (default=/dev/kmsg), not logging\n"; return false; } bool inline_logging = std::getenv("INLINE_LOGGING") ? true : false; Log::get(kmsg_fd, std::cerr, inline_logging); return true; } Log& Log::get(int kmsg_fd, std::ostream& debug_sink, bool inl) { static Log singleton(kmsg_fd, debug_sink, inl); return singleton; } std::unique_ptr Log::get_for_unittest(int kmsg_fd, std::ostream& debug_sink, bool inl) { return std::unique_ptr(new Log(kmsg_fd, debug_sink, inl)); } void Log::kmsgLog(const std::string& buf, const std::string& prefix) const { if (kmsg_fd_ >= 0) { std::string message(buf); if (prefix.size() > 0) { message.insert(0, prefix + ": "); } auto ret = Util::writeFull(kmsg_fd_, message.data(), message.size()); if (ret == -1) { perror("error writing"); OLOG << "Unable to write log to output file"; } } else { OLOG << "kmsg logging disabled b/c kmsg_fd_=" << kmsg_fd_; } OLOG << buf; } void Log::debugLog(std::string&& buf) { // If we are doing inline logging, we don't want to pass the buffer to the // async queue if (inline_) { std::cerr << "(inl) " << buf; return; } std::unique_lock lock(state_.lock); if (buf.size() + state_.curSize > state_.maxSize) { state_.numDiscarded++; return; } auto* q = state_.getCurrentQueue(); q->emplace_back(std::move(buf)); state_.curSize += buf.size(); state_.cv.notify_one(); } void Log::ioThread(std::ostream& debug_sink) { bool io_thread_running = true; while (io_thread_running) { std::vector* q = nullptr; size_t numDiscarded; // Swap the rx/tx queues { std::unique_lock lock(state_.lock); q = state_.getCurrentQueue(); // Wait until we have stuff to write state_.cv.wait( lock, [this, q] { return !state_.ioThreadRunning || q->size(); }); io_thread_running = state_.ioThreadRunning; numDiscarded = state_.numDiscarded; state_.curSize = 0; state_.numDiscarded = 0; state_.ioTick++; // flips the last bit that getCurrentQueue uses } for (auto& buf : *q) { debug_sink << buf; } if (numDiscarded) { debug_sink << "...\n" << numDiscarded << " messages dropped\n...\n"; } debug_sink << std::flush; // clear() doesn't shrink capacity, only invalidates contents q->clear(); } } template <> LogStream& LogStream::operator<<(const Control& ctrl) { switch (ctrl) { case Control::DISABLE: enabled() = false; break; case Control::ENABLE: skip_ = true; enabled() = true; break; // Missing default to protect against future enum vals } return *this; } } // namespace Oomd oomd-0.3.1/src/oomd/Log.h000066400000000000000000000101111362162667600151010ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include #include #include namespace Oomd { class LogBase { public: virtual ~LogBase() = default; virtual void kmsgLog(const std::string& buf, const std::string& prefix) const = 0; virtual void debugLog(std::string&& buf) = 0; }; class Log : public LogBase { public: Log(const Log& other) = delete; Log& operator=(const Log& other) = delete; ~Log() override; static bool init(); static Log& get(int kmsg_fd = -1, std::ostream& debug_sink = std::cerr, bool inl = true); static std::unique_ptr get_for_unittest(int kmsg_fd, std::ostream& debug_sink, bool inl); void kmsgLog(const std::string& buf, const std::string& prefix) const override; void debugLog(std::string&& buf) override; private: struct AsyncLogState { // The IO thread processes one queue, calling thread writes to other std::array, 2> queues; // Last bit is used to choose queue to process uint64_t ioTick{0}; // Notifies the I/O thread to stop bool ioThreadRunning{true}; // We only store maxSize bytes before we start dropping messages size_t curSize{0}; const size_t maxSize{1024 * 1024}; size_t numDiscarded{0}; std::condition_variable cv; std::mutex lock; std::vector* getCurrentQueue() { return &queues[ioTick & 0x01]; } }; // only get() is allowed to construct explicit Log(int kmsg_fd, std::ostream& debug_sink, bool inl); void ioThread(std::ostream& debug_sink); int kmsg_fd_{-1}; bool inline_{true}; std::thread io_thread_; AsyncLogState state_; }; class LogStream { public: enum class Control { DISABLE, ENABLE, }; LogStream(); explicit LogStream(LogBase& sink); ~LogStream(); template LogStream& operator<<(const T& v) { // If we've previously received a control token to enable the logs, // skip_ should be set b/c we didn't want to print an empty log line. // However, in the event that a user follows the control token with // actual log messages, we should make sure to process the entire log. // // Eg // OLOG << LogStream::Control::ENABLE << "we should print this"; if (skip_) { skip_ = false; } // Don't store logs unless we're enabled. This helps with interleaved // disabled and enabled states. Eg // OLOG << LogStream::Control::DISABLE << "blah" // << LogStream::Control::ENABLE << "asdf"; // // Only "asdf" should be printed. // // Without this check, we'd get "blahasdf". if (enabled()) { stream_ << v; } return *this; } private: static bool& enabled(); // Set this flag to skip processing this log message bool skip_{false}; std::ostringstream stream_; LogBase& sink_; }; // Must declare explicit specialization in *namespace* scope (class scope // doesn't count) for some weird reason according to the C++ spec. template <> LogStream& LogStream::operator<<(const Control& ctrl); template static void OOMD_KMSG_LOG(Args&&... args) { Log::get().kmsgLog(std::forward(args)...); } // This has to be a macro so __FILE__ and __LINE__ are captured #define OLOG ::Oomd::LogStream() << "[" << __FILE__ << ":" << __LINE__ << "] " } // namespace Oomd oomd-0.3.1/src/oomd/LogTest.cpp000066400000000000000000000132411362162667600163030ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/OomdContext.h" using namespace Oomd; using namespace testing; class MockLog : public LogBase { public: ~MockLog() override = default; void kmsgLog(const std::string& /* unused */, const std::string& /* unused */) const override { return; } void debugLog(std::string&& buf) override { lines.emplace_back(std::move(buf)); } std::vector lines; }; class LogTestKmsg : public ::testing::Test { public: std::string test_string{"Testing basic logger output!"}; std::string test_prefix{"oomd"}; std::string test_complex_string{ "oomd kill: 10.00 60.00 600.00 Evil 1999 detector:swap,12345MB killer:Evaporate "}; protected: std::pair, std::ifstream> get_logger_and_file() { std::string outfile = "/tmp/logtest.XXXXXX"; int fd = ::mkstemp(&outfile[0]); auto logger = Log::get_for_unittest(fd, std::cerr, /* inl= */ false); std::ifstream result_file(outfile); if (::remove(outfile.c_str()) != 0) { perror("remove"); } return std::make_pair(std::move(logger), std::move(result_file)); } }; /* Test the plain kmsgLog(buf) interface. */ TEST_F(LogTestKmsg, VerifyOutputSimple) { auto logger_and_file = get_logger_and_file(); auto& logger = logger_and_file.first; auto& result_file = logger_and_file.second; logger->kmsgLog(test_string, test_prefix); /* check output */ std::string compare_string; getline(result_file, compare_string); /* verify log contents */ EXPECT_EQ(compare_string, test_prefix.append(": " + test_string)); } TEST(LogTestAsyncDebug, CoupleLines) { std::stringstream sstr; { // Do not use inline logging for our log tests because // 1) we want to test async logging behavior // 2) we correctly destroy the Log object during the test which // correctly joins the async thread auto logger = Log::get_for_unittest(STDERR_FILENO, sstr, /* inl= */ false); logger->debugLog(std::string("line one\n")); logger->debugLog(std::string("line2\n")); } EXPECT_EQ(sstr.str(), "line one\nline2\n"); } TEST(LogTestAsyncDebug, LotsOfLines) { for (int i = 0; i < 10; ++i) { std::stringstream expected; std::stringstream sstr; { // See comment in CoupleLines auto logger = Log::get_for_unittest(STDERR_FILENO, sstr, /* inl= */ false); for (int j = 0; j < 150; ++j) { logger->debugLog(std::to_string(j) + "_line\n"); expected << j << "_line\n"; } } EXPECT_EQ(sstr.str(), expected.str()); } } TEST(LogStream, Basic) { MockLog log; LogStream(log) << "hello world!"; LogStream(log) << "hello world 2!"; ASSERT_EQ(log.lines.size(), 2); EXPECT_EQ(log.lines[0], "hello world!\n"); EXPECT_EQ(log.lines[1], "hello world 2!\n"); } TEST(LogStream, ControlDisable) { MockLog log; LogStream(log) << "one"; LogStream(log) << LogStream::Control::DISABLE; LogStream(log) << "two"; LogStream(log) << "three"; LogStream(log) << LogStream::Control::ENABLE; LogStream(log) << "four"; LogStream(log) << "five"; ASSERT_EQ(log.lines.size(), 3); EXPECT_EQ(log.lines[0], "one\n"); EXPECT_EQ(log.lines[1], "four\n"); EXPECT_EQ(log.lines[2], "five\n"); } TEST(LogStream, ControlDisableEnableAdvanced) { MockLog log; LogStream(log) << "one"; LogStream(log) << LogStream::Control::DISABLE << "after disable"; LogStream(log) << "two"; LogStream(log) << "three"; LogStream(log) << LogStream::Control::ENABLE << "after enable"; ASSERT_EQ(log.lines.size(), 2); EXPECT_EQ(log.lines[0], "one\n"); EXPECT_EQ(log.lines[1], "after enable\n"); log.lines.clear(); LogStream(log) << LogStream::Control::DISABLE << "after disable" << LogStream::Control::ENABLE << "after enable"; ASSERT_EQ(log.lines.size(), 1); EXPECT_EQ(log.lines[0], "after enable\n"); } TEST(LogStream, ControlMany) { MockLog log; LogStream(log) << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << LogStream::Control::DISABLE << LogStream::Control::ENABLE << "hello world"; ASSERT_EQ(log.lines.size(), 1); EXPECT_EQ(log.lines[0], "hello world\n"); } oomd-0.3.1/src/oomd/Main.cpp000066400000000000000000000304711362162667600156120ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/Oomd.h" #include "oomd/PluginRegistry.h" #include "oomd/Stats.h" #include "oomd/StatsClient.h" #include "oomd/config/ConfigCompiler.h" #include "oomd/config/JsonConfigParser.h" #include "oomd/include/Assert.h" #include "oomd/include/CgroupPath.h" #include "oomd/include/CoreStats.h" #include "oomd/include/Defines.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" #ifdef MESON_BUILD #include "Version.h" // @manual #else #define GIT_VERSION "unknown" #endif static constexpr auto kConfigFilePath = "/etc/oomd.json"; static constexpr auto kCgroupFsRoot = "/sys/fs/cgroup"; static constexpr auto kSocketPath = "/run/oomd/oomd-stats.socket"; static const struct Oomd::IOCostCoeffs default_hdd_coeffs = { .read_iops = 1.31e-3, .readbw = 1.13e-7, .write_iops = 2.58e-1, .writebw = 5.04e-7, .trim_iops = 0, .trimbw = 0, }; static const struct Oomd::IOCostCoeffs default_ssd_coeffs = { .read_iops = 1.21e-2, .readbw = 6.25e-7, .write_iops = 1.07e-3, .writebw = 2.61e-7, .trim_iops = 2.37e-2, .trimbw = 9.10e-10, }; static void printUsage() { std::cerr << "usage: oomd [OPTION]...\n\n" "optional arguments:\n" " --help, -h Show this help message and exit\n" " --version, -v Print version and exit\n" " --config, -C CONFIG Config file (default: /etc/oomd.json)\n" " --interval, -i INTERVAL Event loop polling interval (default: 5)\n" " --cgroup-fs, -f FS Cgroup2 filesystem mount point (default: /sys/fs/cgroup)\n" " --check-config, -c CONFIG Check config file (default: /etc/oomd.json)\n" " --list-plugins, -l List all available plugins\n" " --drop-in-dir, -w DIR Directory to watch for drop in configs\n" " --socket-path, -s PATH Specify stats socket path (default: /run/oomd/oomd-stats.socket)\n" " --dump-stats, -d Dump accumulated stats\n" " --reset-stats, -r Reset stats collection\n" " --device DEVS Comma separated : pairs for IO cost calculation (default: none)\n" " --ssd-coeffs COEFFS Comma separated values for SSD IO cost calculation (default: see doc)\n" " --hdd-coeffs COEFFS Comma separated values for HDD IO cost calculation (default: see doc)\n" << std::endl; } static bool system_reqs_met() { // 4.20 mempressure file auto psi = Oomd::Fs::readFileByLine("/proc/pressure/memory"); if (psi.size()) { return true; } // Experimental mempressure file psi = Oomd::Fs::readFileByLine("/proc/mempressure"); if (psi.size()) { return true; } std::cerr << "PSI not detected. Is your system running a new enough kernel?\n"; return false; } static bool cgroup_fs_valid(const std::string& path) { std::string cgroup2ParentPath = Oomd::Fs::getCgroup2MountPoint(); return Oomd::Fs::isUnderParentPath(cgroup2ParentPath, path); } static std::unique_ptr parseConfig( const std::string& flag_conf_file) { std::ifstream conf_file(flag_conf_file, std::ios::in); if (!conf_file.is_open()) { std::cerr << "Could not open confg_file=" << flag_conf_file << std::endl; return nullptr; } std::stringstream buf; buf << conf_file.rdbuf(); Oomd::Config2::JsonConfigParser json_parser; auto ir = json_parser.parse(buf.str()); if (!ir) { std::cerr << "Could not parse conf_file=" << flag_conf_file << std::endl; return nullptr; } return ir; } static Oomd::IOCostCoeffs parseCoeffs(const std::string& str_coeffs) { Oomd::IOCostCoeffs coeffs = {}; auto parts = Oomd::Util::split(str_coeffs, ','); auto coeff_fields = {&coeffs.read_iops, &coeffs.readbw, &coeffs.write_iops, &coeffs.writebw, &coeffs.trim_iops, &coeffs.trimbw}; size_t idx = 0; for (auto& coeff_field : coeff_fields) { *coeff_field = idx >= parts.size() ? 0 : std::stod(parts[idx]); idx++; } return coeffs; } static std::unordered_map parseDevices( const std::string& str_devices) { std::unordered_map io_devs; auto parts = Oomd::Util::split(str_devices, ','); for (const auto& dev_id : parts) { auto dev_type = Oomd::Fs::getDeviceType(dev_id); io_devs[dev_id] = dev_type; } return io_devs; } static void initializeCoreStats() { // Zero out core keys so that there aren't any "missing" keys // until the associated event occurs for (const char* key : Oomd::CoreStats::kAllKeys) { Oomd::setStat(key, 0); } } enum MainOptions { OPT_DEVICE = 256, // avoid collision with char OPT_SSD_COEFFS, OPT_HDD_COEFFS, }; int main(int argc, char** argv) { std::string flag_conf_file = kConfigFilePath; std::string cgroup_fs = kCgroupFsRoot; std::string drop_in_dir; std::string stats_socket_path = kSocketPath; std::string dev_id; int interval = 5; bool should_check_config = false; int option_index = 0; int c = 0; bool should_dump_stats = false; bool should_reset_stats = false; std::unordered_map io_devs; struct Oomd::IOCostCoeffs hdd_coeffs = default_hdd_coeffs; struct Oomd::IOCostCoeffs ssd_coeffs = default_ssd_coeffs; const char* const short_options = "hvC:w:i:f:c:ls:dr"; option long_options[] = { option{"help", no_argument, nullptr, 'h'}, option{"version", no_argument, nullptr, 'v'}, option{"config", required_argument, nullptr, 'C'}, option{"interval", required_argument, nullptr, 'i'}, option{"cgroup-fs", required_argument, nullptr, 'f'}, option{"check-config", required_argument, nullptr, 'c'}, option{"list-plugins", no_argument, nullptr, 'l'}, option{"drop-in-dir", required_argument, nullptr, 'w'}, option{"socket-path", required_argument, nullptr, 's'}, option{"dump-stats", no_argument, nullptr, 'd'}, option{"reset-stats", no_argument, nullptr, 'r'}, option{"device", required_argument, nullptr, OPT_DEVICE}, option{"ssd-coeffs", required_argument, nullptr, OPT_SSD_COEFFS}, option{"hdd-coeffs", required_argument, nullptr, OPT_HDD_COEFFS}, option{nullptr, 0, nullptr, 0}}; while ((c = getopt_long( argc, argv, short_options, long_options, &option_index)) != -1) { size_t parsed_len; bool parse_error = false; switch (c) { case 'h': printUsage(); return 0; case 'v': std::cout << GIT_VERSION << std::endl; return 0; case 'C': flag_conf_file = std::string(optarg); break; case 'c': should_check_config = true; flag_conf_file = std::string(optarg); break; case 'w': drop_in_dir = std::string(optarg); break; case 'l': std::cerr << "List of plugins oomd was compiled with:\n"; for (const auto& plugin_name : Oomd::getPluginRegistry().getRegistered()) { std::cerr << " " << plugin_name << "\n"; } return 0; case 'i': try { interval = std::stoi(optarg, &parsed_len); } catch (const std::invalid_argument& e) { parse_error = true; } if (parse_error || interval < 1 || parsed_len != strlen(optarg)) { std::cerr << "Interval not a >0 integer: " << optarg << std::endl; return 1; } break; case 'f': cgroup_fs = std::string(optarg); break; case 's': stats_socket_path = std::string(optarg); break; case 'd': should_dump_stats = true; break; case 'r': should_reset_stats = true; break; case OPT_DEVICE: try { io_devs = parseDevices(optarg); } catch (const Oomd::Fs::bad_control_file& e) { std::cerr << "Invalid devices: " << e.what() << '\n'; return 1; } break; case OPT_SSD_COEFFS: try { ssd_coeffs = parseCoeffs(optarg); } catch (const std::invalid_argument& e) { std::cerr << "Invalid SSD coefficients: " << e.what() << '\n'; return 1; } break; case OPT_HDD_COEFFS: try { hdd_coeffs = parseCoeffs(optarg); } catch (const std::invalid_argument& e) { std::cerr << "Invalid HDD coefficients: " << e.what() << '\n'; return 1; } break; case 0: break; case '?': std::cerr << "Unknown option or missing argument\n"; printUsage(); return 1; default: return 1; } } if (optind < argc) { std::cerr << "Non-option argument is not supported: "; while (optind < argc) { std::cerr << "\"" << argv[optind++] << "\" "; } std::cerr << std::endl; printUsage(); return 1; } if (should_dump_stats) { try { Oomd::StatsClient client(stats_socket_path); auto map = client.getStats(); if (!map) { std::cerr << "Failed to retrieve stats" << std::endl; return 1; } Json::Value root(Json::objectValue); for (const auto& pair : *map) { root[pair.first] = pair.second; } std::cout << root.toStyledString() << std::endl; } catch (const std::runtime_error& e) { std::cerr << e.what() << std::endl; return 1; } if (!should_reset_stats) { return 0; } } if (should_reset_stats) { try { Oomd::StatsClient client(stats_socket_path); int res = client.resetStats(); if (res != 0) { std::cerr << "Reset stats error: received error code= " << res << std::endl; return 1; } } catch (const std::runtime_error& e) { std::cerr << e.what() << std::endl; return 1; } return 0; } // Init oomd logging code // // NB: do not use OLOG before initializing logging. Doing so will disable // kmsg logging (due to some weird setup code required to get unit testing // correct). Be careful not to make any oomd library calls before initing // logging. if (!Oomd::Log::init()) { std::cerr << "Logging failed to initialize. Try running with sudo\n"; return 1; } if (!Oomd::Stats::init(stats_socket_path)) { OLOG << "Stats module failed to initialize"; return 1; } initializeCoreStats(); if (should_check_config) { auto ir = parseConfig(flag_conf_file); if (!ir) { return 1; } auto engine = Oomd::Config2::compile(*ir); if (!engine) { OLOG << "Config is not valid"; return 1; } return 0; } if (!system_reqs_met()) { std::cerr << "System requirements not met\n"; return EXIT_CANT_RECOVER; } if (!cgroup_fs_valid(cgroup_fs)) { std::cerr << cgroup_fs << " is not a valid cgroup2 filesystem" << std::endl; return EXIT_CANT_RECOVER; } std::cerr << "oomd running with conf_file=" << flag_conf_file << " interval=" << interval << std::endl; auto ir = parseConfig(flag_conf_file); if (!ir) { return EXIT_CANT_RECOVER; } auto engine = Oomd::Config2::compile(*ir); if (!engine) { OLOG << "Config failed to compile"; return EXIT_CANT_RECOVER; } Oomd::Oomd oomd( std::move(ir), std::move(engine), interval, cgroup_fs, drop_in_dir, io_devs, hdd_coeffs, ssd_coeffs); return oomd.run(); } oomd-0.3.1/src/oomd/Oomd.cpp000066400000000000000000000523621362162667600156270ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/Oomd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/config/ConfigCompiler.h" #include "oomd/config/JsonConfigParser.h" #include "oomd/include/Assert.h" #include "oomd/include/Defines.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" static constexpr auto kMaxEvents = 10; namespace { /* * Helper function that resolves a set of wildcarded cgroup paths. * * @returns a set of resolved cgroup paths */ std::unordered_set resolveCgroupPaths( const std::unordered_set& cgroups) { std::unordered_set ret; for (const auto& cgroup : cgroups) { // TODO: see what the performance penalty of walking the FS // (in resolveWildcardPath) every time is. If it's a big penalty, // we could search `absolutePath` for any fnmatch operators and // only resolve if we find symbols. auto resolved_paths = Oomd::Fs::resolveWildcardPath(cgroup); for (const auto& resolved_path : resolved_paths) { size_t idx = 0; // Use idx to mark where non-cgroupfs part of path begins idx += cgroup.cgroupFs().size(); if (resolved_path.size() > cgroup.cgroupFs().size()) { // Remove '/' after cgroupfs in path idx += 1; } if (idx <= resolved_path.size()) { std::string cgroup_relative = resolved_path.substr(idx); ret.emplace(cgroup.cgroupFs(), std::move(cgroup_relative)); } } } return ret; } } // namespace namespace Oomd { Oomd::Oomd( std::unique_ptr ir_root, std::unique_ptr engine, int interval, const std::string& cgroup_fs, const std::string& drop_in_dir, const std::unordered_map& io_devs, const IOCostCoeffs& hdd_coeffs, const IOCostCoeffs& ssd_coeffs) : interval_(interval), cgroup_fs_(cgroup_fs), ir_root_(std::move(ir_root)), engine_(std::move(engine)), drop_in_dir_(drop_in_dir), io_devs_(io_devs), hdd_coeffs_(hdd_coeffs), ssd_coeffs_(ssd_coeffs) { // Ensure that each monitored cgroup's cgroup fs is the same as the one // passed in by the command line if (engine_) { // Tests will pass in a nullptr // First ensure cgroup fs is uniform for (const auto& cgroup : engine_->getMonitoredResources()) { if (cgroup.cgroupFs() != cgroup_fs_) { resources_.emplace(cgroup_fs_, cgroup.relativePath()); } else { resources_.emplace(cgroup); } } // Then make sure all parent cgroups are pulled in as well. This is // necessary so plugins can walk a prepopulated tree. for (const auto& cgroup : resources_) { if (cgroup.isRoot()) { continue; } CgroupPath parent = cgroup.getParent(); while (!parent.isRoot()) { resources_.emplace(parent); parent = parent.getParent(); } } } // Sanitize the drop in dir a little if (drop_in_dir_.size() && drop_in_dir_.at(drop_in_dir_.size() - 1) == '/') { // Delete the trailing '/' drop_in_dir_.erase(drop_in_dir.size() - 1); } } int64_t Oomd::calculateProtection( const CgroupPath& cgroup, OomdContext& ctx, std::unordered_map& cache) { if (cache.find(cgroup) != cache.end()) { return cache.at(cgroup); } auto node = ctx.getCgroupNode(cgroup); OCHECK_EXCEPT(node, std::runtime_error("cgroup missing from OomdContext")); auto l_func = [](const CgroupContext& c) -> double { return std::min(c.current_usage, std::max(c.memory_min, c.memory_low)); }; std::function)> p_func = [&](const std::shared_ptr node) -> double { auto parent = node->parent.lock(); if (parent->path.isRoot()) { // We're at a top level cgroup where P(cgrp) == L(cgrp) return l_func(node->ctx); } double l_sum_children = 0; for (const auto& child : parent->children) { l_sum_children += l_func(child->ctx); } // If the cgroup isn't using any memory then it's trivially true it's // not receiving any protection if (l_sum_children == 0) { return 0; } return std::min( l_func(node->ctx), p_func(parent) * l_func(node->ctx) / l_sum_children); }; int64_t ret = p_func(node); cache[cgroup] = ret; return ret; } double Oomd::calculateIOCostCumulative(const IOStat& io_stat) { double cost_cumulative = 0.0; // calculate the sum of cumulative io cost on all devices. for (auto& stat : io_stat) { // only keep stats from devices we care if (io_devs_.find(stat.dev_id) == io_devs_.end()) { continue; } IOCostCoeffs coeffs; switch (io_devs_.at(stat.dev_id)) { case DeviceType::SSD: coeffs = ssd_coeffs_; break; case DeviceType::HDD: coeffs = hdd_coeffs_; break; } // Dot product between dev io stat and io cost coeffs. A more sensible way // is to do dot product between rate of change (bandwidth, iops) with coeffs // but since the coeffs are constant, we can calculate rate of change later. cost_cumulative += stat.rios * coeffs.read_iops + stat.rbytes * coeffs.readbw + stat.wios * coeffs.write_iops + stat.wbytes * coeffs.writebw + stat.dios * coeffs.trim_iops + stat.dbytes * coeffs.trimbw; } return cost_cumulative; } ResourcePressure Oomd::readIopressureWarnOnce(const std::string& path) { ResourcePressure io_pressure; // Older kernels don't have io.pressure, nan them out io_pressure = {std::nanf(""), std::nanf(""), std::nanf("")}; try { if (!warned_io_pressure_.count(path)) { io_pressure = Fs::readIopressure(path); } } catch (const std::exception& ex) { warned_io_pressure_.emplace(path); OLOG << "IO pressure unavailable on " << path << ": " << ex.what(); } return io_pressure; } bool Oomd::updateContextRoot(const CgroupPath& path, OomdContext& ctx) { // Note we not not collect _every_ piece of data that makes // sense for the root host. Feel free to add more as needed. auto pressures = Fs::readMempressure("/"); auto io_pressure = readIopressureWarnOnce("/"); auto current = Fs::readMemcurrent("/"); auto nr_dying_descendants = Fs::getNrDyingDescendants(path.cgroupFs()); ctx.setCgroupContext( path, {.pressure = pressures, .io_pressure = io_pressure, .current_usage = current, .nr_dying_descendants = nr_dying_descendants}); return true; } bool Oomd::updateContextCgroup(const CgroupPath& path, OomdContext& ctx) { std::string absolute_cgroup_path = path.absolutePath(); // Warn once if memory controller is not enabled on target cgroup auto controllers = Fs::readControllers(absolute_cgroup_path); if (!std::any_of(controllers.begin(), controllers.end(), [](std::string& s) { return s == "memory"; })) { if (!warned_mem_controller_.count(absolute_cgroup_path)) { OLOG << "WARNING: cgroup memory controller not enabled on " << absolute_cgroup_path << ". oomd will ignore it."; warned_mem_controller_.emplace(absolute_cgroup_path); } // Can't extract much info if memory controller isn't enabled return false; } auto current = Fs::readMemcurrent(absolute_cgroup_path); auto pressures = Fs::readMempressure(absolute_cgroup_path); auto memlow = Fs::readMemlow(absolute_cgroup_path); auto memmin = Fs::readMemmin(absolute_cgroup_path); auto memhigh = Fs::readMemhigh(absolute_cgroup_path); auto memmax = Fs::readMemmax(absolute_cgroup_path); auto swap_current = Fs::readSwapCurrent(absolute_cgroup_path); auto memory_stats = Fs::getMemstat(absolute_cgroup_path); auto anon_usage = memory_stats["anon"]; auto io_pressure = readIopressureWarnOnce(absolute_cgroup_path); int64_t memhigh_tmp = 0; try { memhigh_tmp = Fs::readMemhightmp(absolute_cgroup_path); } catch (const Fs::bad_control_file& ex) { } double io_cost_cumulative = 0; if (io_devs_.size() != 0) { IOStat io_stat; if (std::any_of(controllers.begin(), controllers.end(), [](std::string& s) { return s == "io"; })) { io_stat = Fs::readIostat(absolute_cgroup_path); } else { if (!warned_io_stat_.count(absolute_cgroup_path)) { warned_io_stat_.emplace(absolute_cgroup_path); OLOG << "WARNING: cgroup io controller unavailable on " << absolute_cgroup_path << ". io cost will be zero."; } io_stat = {}; } io_cost_cumulative = calculateIOCostCumulative(io_stat); } auto nr_dying_descendants = Fs::getNrDyingDescendants(absolute_cgroup_path); ctx.setCgroupContext( path, {.pressure = pressures, .io_pressure = io_pressure, .current_usage = current, .memory_low = memlow, .swap_usage = swap_current, .anon_usage = anon_usage, .memory_min = memmin, .memory_high = memhigh, .memory_high_tmp = memhigh_tmp, .memory_max = memmax, .io_cost_cumulative = io_cost_cumulative, .nr_dying_descendants = nr_dying_descendants}); return true; } void Oomd::updateContext( const std::unordered_set& cgroups, OomdContext& ctx) { OomdContext new_ctx; // Caching results helps reduce tree walks std::unordered_map protection_cache; auto resolved = resolveCgroupPaths(cgroups); for (const auto& resolved_cgroup : resolved) { // Only care about subtree cgroups, not the cgroup files if (!Fs::isDir(resolved_cgroup.absolutePath())) { continue; } // Despite checking just above if the cgroup directory is valid, // we can still race with a cgroup disappearing. When that happens, // simply abort updating the cgroup. try { if (resolved_cgroup.isRoot()) { updateContextRoot(resolved_cgroup, new_ctx); } else { updateContextCgroup(resolved_cgroup, new_ctx); } } catch (const Fs::bad_control_file& ex) { OLOG << "Caught bad_control_file: " << ex.what() << ". This is OK -- " << "cgroups can disappear when a managed process exits"; continue; } } // Update values that depend on the rest of OomdContext being up to date for (auto& key : new_ctx.cgroups()) { // This information isn't really relevant to the root host. Skip it. if (key.isRoot()) { continue; } auto new_cgroup_ctx = new_ctx.getCgroupContext(key); // copy float prev_avg = 0; // make the initial io cost zero double prev_io_cost_cum = new_cgroup_ctx.io_cost_cumulative; if (ctx.hasCgroupContext(key)) { prev_avg = ctx.getCgroupContext(key).average_usage; prev_io_cost_cum = ctx.getCgroupContext(key).io_cost_cumulative; } // Update running average new_cgroup_ctx.average_usage = prev_avg * ((average_size_decay_ - 1) / average_size_decay_) + (new_cgroup_ctx.current_usage / average_size_decay_); // Update io cost new_cgroup_ctx.io_cost_rate = (new_cgroup_ctx.io_cost_cumulative - prev_io_cost_cum) / interval_.count(); // Update protection overage new_cgroup_ctx.memory_protection = calculateProtection(key, new_ctx, protection_cache); new_ctx.setCgroupContext(key, new_cgroup_ctx); } // Update information about swapfree SystemContext system_ctx; auto swaps = Fs::readFileByLine("/proc/swaps"); // For each swap, tally up used and total for (size_t i = 1; i < swaps.size(); ++i) { auto parts = Util::split(swaps[i], '\t'); // The /proc/swaps format is pretty bad. The first field is padded by // spaces but the rest of the fields are padded by '\t'. Since we don't // really care about the first field, we'll just split by '\t'. OCHECK_EXCEPT( parts.size() == 4, std::runtime_error("/proc/swaps malformed")); system_ctx.swaptotal += std::stoll(parts[1]) * 1024; // Values are in KB system_ctx.swapused += std::stoll(parts[2]) * 1024; // Values are in KB } new_ctx.setSystemContext(system_ctx); // update object state ctx = std::move(new_ctx); } int Oomd::deregisterDropInWatcherFromEventLoop() { char buf[1024]; int ret = 0; // Remove watch for stale fd and wd and reset them to their // initial values. Caveat: If we try to remove the watch for // drop_in_dir that has been deleted, inotify_rm_watch() returns // EINVAL as the watch is automatically deleted when the directory // was removed. Only remove watches if they're files in the directory // and not the directory itself. if (!drop_in_dir_deleted_ && ::inotify_rm_watch(inotifyfd_, inotifywd_) < 0) { OLOG << "inotify_rm_watch: " << ::strerror_r(errno, buf, sizeof(buf)); ret = 1; } if (::epoll_ctl(epollfd_, EPOLL_CTL_DEL, inotifyfd_, nullptr) < 0) { OLOG << "epoll_ctl: " << ::strerror_r(errno, buf, sizeof(buf)); ret = 1; } inotifyfd_ = -1; inotifywd_ = -1; return ret; } int Oomd::prepDropInWatcherEventLoop(const std::string& dir) { char buf[1024]; inotifyfd_ = ::inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (inotifyfd_ < 0) { OLOG << "inotify_init1: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } uint32_t mask = IN_DELETE | IN_MODIFY | IN_MOVE | IN_ONLYDIR | IN_MOVE_SELF | IN_DELETE_SELF; if ((inotifywd_ = ::inotify_add_watch(inotifyfd_, dir.c_str(), mask)) < 0) { OLOG << "inotify_add_watch: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } // Add inotifyfd to epoll set struct epoll_event ev; std::memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = inotifyfd_; if (::epoll_ctl(epollfd_, EPOLL_CTL_ADD, inotifyfd_, &ev) < 0) { OLOG << "epoll_ctl: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } return 0; } int Oomd::prepDropInWatcher(const std::string& dir) { if (!Fs::isDir(dir)) { OLOG << "Error: " << dir << " is not a directory"; return 1; } // Load existing drop in configs before setting up inotify hooks // so we don't race with configs dropping in auto de = Fs::readDir(dir, Fs::DE_FILE); std::sort(de.files.begin(), de.files.end()); // Provide some determinism for (const auto& config : de.files) { processDropInAdd(config); } if (prepDropInWatcherEventLoop(dir)) { return 1; } return 0; } int Oomd::prepEventLoop(const std::chrono::seconds& interval) { char buf[1024]; timerfd_ = ::timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); if (timerfd_ < 0) { OLOG << "timerfd_create: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } struct itimerspec ts; std::memset(&ts, 0, sizeof(ts)); ts.it_value.tv_sec = interval.count(); // initial expiration ts.it_interval.tv_sec = interval.count(); // periodic expiration if (::timerfd_settime(timerfd_, 0, &ts, nullptr) < 0) { OLOG << "timerfd_settime: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } epollfd_ = ::epoll_create1(EPOLL_CLOEXEC); if (epollfd_ < 0) { OLOG << "epoll_create1: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } // Add timerfd to epoll set struct epoll_event ev; std::memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = timerfd_; if (::epoll_ctl(epollfd_, EPOLL_CTL_ADD, timerfd_, &ev) < 0) { OLOG << "epoll_ctl: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } // Set up drop in config watcher if necessary if (drop_in_dir_.size()) { int ret = prepDropInWatcher(drop_in_dir_); if (ret) { return ret; } } return 0; } void Oomd::processDropInRemove(const std::string& file) { // Ignore dot files if (file.empty() || (file.size() && file.at(0) == '.')) { return; } OLOG << "Removing drop in config=" << file; size_t tag = std::hash{}(file); engine_->removeDropInConfig(tag); } void Oomd::processDropInAdd(const std::string& file) { // Ignore dot files if (file.empty() || (file.size() && file.at(0) == '.')) { return; } // First remove then re-add. We don't do in place modifications as it'll // be complicated for the code and it probably wouldn't be what the user // expects. The user probably expects the entire drop in config is reset // and added to the front of the LIFO queue. processDropInRemove(file); OLOG << "Adding drop in config=" << file; std::ifstream dropin_file(drop_in_dir_ + '/' + file, std::ios::in); if (!dropin_file.is_open()) { OLOG << "Could not open drop in config=" << file; return; } std::stringstream buf; buf << dropin_file.rdbuf(); Config2::JsonConfigParser json_parser; std::unique_ptr dropin_root; try { dropin_root = json_parser.parse(buf.str()); } catch (const std::exception& e) { OLOG << "Caught: " << e.what(); OLOG << "Failed to inject drop in config into engine"; return; } if (!dropin_root) { OLOG << "Could not parse drop in config=" << file; OLOG << "Failed to inject drop in config into engine"; return; } auto unit = Config2::compileDropIn(*ir_root_, *dropin_root); if (!unit.has_value()) { OLOG << "Could not compile drop in config"; OLOG << "Failed to inject drop in config into engine"; return; } size_t tag = std::hash{}(file); for (size_t i = 0; i < unit->rulesets.size(); ++i) { if (engine_->addDropInConfig(tag, std::move(unit->rulesets.at(i)))) { resources_.insert(unit->resources.cbegin(), unit->resources.cend()); } else { OLOG << "Failed to inject drop in config into engine"; return; } } } int Oomd::processDropInWatcher(int fd) { const struct inotify_event* event; char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); while (true) { int len = ::read(fd, buf, sizeof(buf)); if (len < 0 && errno != EAGAIN) { OLOG << "read: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } if (len <= 0) { break; } for (char* ptr = buf; ptr < (buf + len); ptr += sizeof(struct inotify_event) + event->len) { event = reinterpret_cast(ptr); if (event->mask & (IN_MOVED_TO | IN_MODIFY)) { // Remove and re-add drop in if a file has been added to the // watched directory processDropInAdd(event->name); } else if (event->mask & (IN_DELETE | IN_MOVED_FROM)) { // Remove drop in if file has been moved from or removed from // the watched directory processDropInRemove(event->name); } else if (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) { // Remove stale watch descriptor for drop in if watched file or // directory itself is moved or deleted drop_in_dir_deleted_ = true; if (deregisterDropInWatcherFromEventLoop()) { return 1; } } } } return 0; } int Oomd::processEventLoop() { struct epoll_event events[kMaxEvents]; char buf[1024]; int ret; int n; do { n = ::epoll_wait(epollfd_, events, kMaxEvents, -1); } while (n < 0 && errno == EINTR); if (n < 0) { OLOG << "epoll_wait: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } for (int i = 0; i < n; ++i) { int fd = events[i].data.fd; if (fd == timerfd_) { uint64_t expired = 0; ret = ::read(fd, &expired, sizeof(expired)); if (ret < 0) { OLOG << "read: " << ::strerror_r(errno, buf, sizeof(buf)); return 1; } // If drop_in_dir_ exists again and contains config files, // set up the watch if (drop_in_dir_deleted_ && Fs::isDir(drop_in_dir_)) { ret = prepDropInWatcher(drop_in_dir_); if (ret) { return ret; } drop_in_dir_deleted_ = false; } } else if (fd == inotifyfd_) { ret = processDropInWatcher(fd); if (ret) { return ret; } } else { OLOG << "Unknown fd=" << fd << " in event loop"; return 1; } } return 0; } int Oomd::run() { OomdContext ctx; int ret; if (!engine_) { OLOG << "Could not run engine. Your config file is probably invalid\n"; return EXIT_CANT_RECOVER; } ret = prepEventLoop(interval_); if (ret) { return ret; } OLOG << "Running oomd"; while (true) { try { // Sleeps and handles events ret = processEventLoop(); if (ret) { return ret; } updateContext(resources_, ctx); // Run all the plugins engine_->runOnce(ctx); } catch (const std::exception& ex) { OLOG << "Caught exception: " << ex.what(); return 1; } } return 0; } } // namespace Oomd oomd-0.3.1/src/oomd/Oomd.h000066400000000000000000000073771362162667600153020ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include #include #include #include #include "oomd/config/ConfigTypes.h" #include "oomd/engine/Engine.h" #include "oomd/include/CgroupPath.h" namespace Oomd { class Oomd { public: Oomd( std::unique_ptr ir_root, std::unique_ptr engine, int interval, const std::string& cgroup_fs, const std::string& drop_in_dir, const std::unordered_map& io_devs = {}, const IOCostCoeffs& hdd_coeffs = {}, const IOCostCoeffs& ssd_coeffs = {}); virtual ~Oomd() = default; /* * This method updates @param ctx with the status of all the cgroups * in @param cgroups. */ void updateContext( const std::unordered_set& cgroups, OomdContext& ctx); int run(); private: /* * Calculate @param cgroup memory protection, taking into account actual * distribution of memory protection. * * Let's say L(cgrp) is the protection amount a cgroup has according to its * own config, P(cgrp) is the amount of actual protection it gets. * * Let L(cgrp) = min(cgrp.memory.current, max(cgrp.memory.min, * cgrp.memory.low)) * * Then, P(cgpr) = min(L(cgrp), P(parent) * L(cgrp) / (Sum of L(child))) for * each child of parent) */ int64_t calculateProtection( const CgroupPath& cgroup, OomdContext& ctx, std::unordered_map& cache); double calculateIOCostCumulative(const IOStat& io_stat); ResourcePressure readIopressureWarnOnce(const std::string& path); bool updateContextRoot(const CgroupPath& path, OomdContext& ctx); bool updateContextCgroup(const CgroupPath& path, OomdContext& ctx); int prepDropInWatcher(const std::string& dir); int prepDropInWatcherEventLoop(const std::string& dir); int deregisterDropInWatcherFromEventLoop(); int prepEventLoop(const std::chrono::seconds& interval); void processDropInRemove(const std::string& file); void processDropInAdd(const std::string& file); int processDropInWatcher(int fd); int processEventLoop(); int epollfd_{-1}; int timerfd_{-1}; int inotifyfd_{-1}; int inotifywd_{-1}; bool drop_in_dir_deleted_{false}; // runtime settings std::chrono::seconds interval_{0}; std::string cgroup_fs_; const double average_size_decay_{ 4}; // TODO(dlxu): migrate to ring buffer for raw datapoints so plugins // can calculate weighted average themselves std::unique_ptr ir_root_; std::unique_ptr engine_; Engine::MonitoredResources resources_; std::unordered_set warned_io_pressure_; std::unordered_set warned_io_stat_; std::unordered_set warned_mem_controller_; std::string drop_in_dir_; // root io device IDs (:) and their device types (SSD/HDD) std::unordered_map io_devs_; IOCostCoeffs hdd_coeffs_; IOCostCoeffs ssd_coeffs_; }; } // namespace Oomd oomd-0.3.1/src/oomd/OomdContext.cpp000066400000000000000000000201701362162667600171640ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/OomdContext.h" #include #include #include "oomd/Log.h" #include "oomd/include/Assert.h" #include "oomd/util/Fs.h" namespace Oomd { CgroupNode::CgroupNode(CgroupPath p) : path(std::move(p)) {} bool OomdContext::hasCgroupContext(const CgroupPath& path) const { return memory_state_.find(path) != memory_state_.end(); } std::vector OomdContext::cgroups() const { std::vector keys; for (const auto& pair : memory_state_) { keys.emplace_back(pair.first); } return keys; } CgroupContext& OomdContext::getMutableCgroupContext( const CgroupPath& path) const { if (!hasCgroupContext(path)) { throw std::invalid_argument("Cgroup not present"); } return memory_state_.at(path)->ctx; } const CgroupContext& OomdContext::getCgroupContext( const CgroupPath& path) const { return getMutableCgroupContext(path); } std::shared_ptr OomdContext::getCgroupNode( const CgroupPath& path) const { if (!hasCgroupContext(path)) { return nullptr; } return memory_state_.at(path); } void OomdContext::setCgroupContext( const CgroupPath& path, CgroupContext context) { memory_state_[path] = addToTree(path, context); } std::vector> OomdContext::reverseSort( std::function getKey) { std::vector> vec; for (const auto& pair : memory_state_) { vec.emplace_back( std::pair{pair.first, pair.second->ctx}); } if (getKey) { reverseSort(vec, getKey); } return vec; } void OomdContext::reverseSort( std::vector>& vec, std::function getKey) { std::sort( vec.begin(), vec.end(), [getKey]( std::pair& first, std::pair& second) { // Want to sort in reverse order (largest first), so return // true if first element is ordered before second element return getKey(first.second) > getKey(second.second); }); } const ActionContext& OomdContext::getActionContext() const { return action_context_; } void OomdContext::setActionContext(const ActionContext& context) { action_context_ = context; } const SystemContext& OomdContext::getSystemContext() const { return system_ctx_; } void OomdContext::setSystemContext(const SystemContext& context) { system_ctx_ = context; } void OomdContext::dump() { dumpOomdContext(reverseSort()); } void OomdContext::dumpOomdContext( const std::vector>& vec, const bool skip_negligible) { OLOG << "Dumping OomdContext: "; for (const auto& ms : vec) { if (skip_negligible) { // don't show if <1% pressure && <.1% usage auto meminfo = Fs::getMeminfo(); const float press_min = 1; const int64_t mem_min = meminfo["MemTotal"] / 1000; const int64_t swap_min = meminfo["SwapTotal"] / 1000; if (!(ms.second.pressure.sec_10 >= press_min || ms.second.pressure.sec_60 >= press_min || ms.second.pressure.sec_600 >= press_min || ms.second.io_pressure.sec_10 >= press_min || ms.second.io_pressure.sec_60 >= press_min || ms.second.io_pressure.sec_600 >= press_min || ms.second.current_usage > mem_min || ms.second.average_usage > mem_min || ms.second.swap_usage > swap_min)) { continue; } } OLOG << "name=" << ms.first.relativePath(); OLOG << " pressure=" << ms.second.pressure.sec_10 << ":" << ms.second.pressure.sec_60 << ":" << ms.second.pressure.sec_600 << "-" << ms.second.io_pressure.sec_10 << ":" << ms.second.io_pressure.sec_60 << ":" << ms.second.io_pressure.sec_600; OLOG << " mem=" << (ms.second.current_usage >> 20) << "MB" << " mem_avg=" << (ms.second.average_usage >> 20) << "MB" << " mem_low=" << (ms.second.memory_low >> 20) << "MB" << " mem_min=" << (ms.second.memory_min >> 20) << "MB" << " mem_high=" << (ms.second.memory_high >> 20) << "MB" << " mem_high_tmp=" << (ms.second.memory_high_tmp >> 20) << "MB" << " mem_max=" << (ms.second.memory_max >> 20) << "MB" << " mem_prot=" << (ms.second.memory_protection >> 20) << "MB" << " anon=" << (ms.second.anon_usage >> 20) << "MB" << " swap_usage=" << (ms.second.swap_usage >> 20) << "MB"; OLOG << " io_cost_cumulative=" << ms.second.io_cost_cumulative << " io_cost_rate=" << ms.second.io_cost_rate; } } void OomdContext::removeSiblingCgroups( const std::unordered_set& ours, std::vector>& vec) { vec.erase( std::remove_if( vec.begin(), vec.end(), [&](const auto& pair) { // Remove this cgroup if does not match any of ours bool found = false; for (const auto& our : ours) { if (our.cgroupFs() == pair.first.cgroupFs() && !::fnmatch( our.relativePath().c_str(), pair.first.relativePath().c_str(), 0)) { found = true; } } return !found; }), vec.end()); } std::shared_ptr OomdContext::addToTree( const CgroupPath& path, CgroupContext ctx) { std::shared_ptr node = findInTree(path); if (node) { node->ctx = std::move(ctx); node->isEmptyBranch = false; return node; } // Didn't find the node; add it return addToTreeHelper(path, std::move(ctx)); } std::shared_ptr OomdContext::addToTreeHelper( const CgroupPath& path, CgroupContext ctx) { // Base case: we're trying to add the root if (path.isRoot()) { if (!root_) { root_ = std::make_shared(path); root_->ctx = std::move(ctx); } else { // Only one cgroup root is allowed OCHECK_EXCEPT( path == root_->path, std::invalid_argument("Multiple cgroup FS detected")); } return root_; } // First find our parent CgroupPath p = path.getParent(); auto parent = findInTree(p); // Create our parent if we need to if (!parent) { parent = addToTreeHelper(p, CgroupContext{}); parent->isEmptyBranch = true; } // Now add ourselves as a child auto us = std::make_shared(path); us->ctx = std::move(ctx); us->parent = parent; parent->children.emplace_back(us); return us; } std::shared_ptr OomdContext::findInTree( const CgroupPath& path) const { if (!root_) { return nullptr; } OCHECK_EXCEPT( path.cgroupFs() == root_->path.cgroupFs(), std::invalid_argument("Multiple cgroup FS detected")); auto n = root_; const auto& parts = path.relativePathParts(); // We walk down the tree one branch at a time trying to match each branch // with any potential children for (size_t i = 0; i < parts.size(); ++i) { std::shared_ptr next = nullptr; for (auto child : n->children) { const auto& child_parts = child->path.relativePathParts(); if (parts.at(i) == child_parts.at(child_parts.size() - 1)) { next = child; break; } } if (!next) { return nullptr; } n = next; } return n; } } // namespace Oomd oomd-0.3.1/src/oomd/OomdContext.h000066400000000000000000000110261362162667600166310ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include #include #include #include "oomd/include/CgroupPath.h" #include "oomd/include/Types.h" namespace Oomd { struct ActionContext { std::string ruleset; std::string detectorgroup; }; struct CgroupNode { explicit CgroupNode(CgroupPath p); ~CgroupNode() = default; CgroupPath path; CgroupContext ctx; // Is this node holding actual data or are we simply a branch for a leaf bool isEmptyBranch{false}; std::weak_ptr parent; std::vector> children; }; class OomdContext { public: OomdContext() {} // = default warns about dynamic exceptions ~OomdContext() = default; OomdContext(OomdContext&& other) noexcept = default; OomdContext& operator=(OomdContext&& other) = default; /* * @returns whether or not OomdContext holds a particular cgroup */ bool hasCgroupContext(const CgroupPath& path) const; /* * @returns all the stored cgroup paths */ std::vector cgroups() const; /* * @returns a CgroupContext reference associated with @param name * @throws std::invalid_argument for missing cgroup */ const CgroupContext& getCgroupContext(const CgroupPath& path) const; /* * Mutable variant of getCgroupContext(). Use only when necessary. */ CgroupContext& getMutableCgroupContext(const CgroupPath& path) const; /* * @returns a CgroupNode* if cgroup is present, nullptr otherwise */ std::shared_ptr getCgroupNode(const CgroupPath& path) const; /* * Assigns a mapping of cgroup -> CgroupContext */ void setCgroupContext(const CgroupPath& path, CgroupContext context); /* * Manipulates CgroupContexts into helpful other helpful datastructures * * @param getKey is a lambda that accesses the key you want to reverse sort by */ std::vector> reverseSort( std::function getKey = nullptr); /* * In place sorts @param vec. Similar to @method * reverseSort(std::function<...>) */ static void reverseSort( std::vector>& vec, std::function getKey); /* * Removes all cgroups from @param vec that do not match @param ours. * * This is useful in plugins that are assigned a set of cgroups to monitor. * Those plugins often need a way to remove all the cgroups they do not * care about. */ static void removeSiblingCgroups( const std::unordered_set& ours, std::vector>& vec); /* * Dumps OomdContext state to stderr */ void dump(); static void dumpOomdContext( const std::vector>& vec, const bool skip_negligible = false); /* * Used to let action plugins know which ruleset and detector group * triggered it */ const ActionContext& getActionContext() const; void setActionContext(const ActionContext& context); /* * Used to let action plugins retrieve and set info stored in struct */ const SystemContext& getSystemContext() const; void setSystemContext(const SystemContext& context); private: std::shared_ptr addToTree( const CgroupPath& path, CgroupContext ctx); std::shared_ptr addToTreeHelper( const CgroupPath& path, CgroupContext ctx); std::shared_ptr findInTree(const CgroupPath& path) const; std::shared_ptr root_{nullptr}; // Read cache so we don't have to walk the tree for read ops std::unordered_map> memory_state_; ActionContext action_context_; SystemContext system_ctx_; }; } // namespace Oomd oomd-0.3.1/src/oomd/OomdContextTest.cpp000066400000000000000000000133431362162667600200300ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "oomd/OomdContext.h" using namespace Oomd; using namespace testing; class OomdContextTest : public ::testing::Test { public: OomdContextTest() = default; OomdContext ctx; }; TEST_F(OomdContextTest, MoveConstructor) { CgroupPath path("/sys/fs/cgroup", "asdf"); EXPECT_FALSE(ctx.hasCgroupContext(path)); OomdContext other; other.setCgroupContext(path, CgroupContext{{}, {}, 1, 2, 3}); ctx = std::move(other); ASSERT_TRUE(ctx.hasCgroupContext(path)); auto moved_cgroup_ctx = ctx.getCgroupContext(path); EXPECT_EQ(moved_cgroup_ctx.current_usage, 1); EXPECT_EQ(moved_cgroup_ctx.average_usage, 2); EXPECT_EQ(moved_cgroup_ctx.memory_low, 3); } TEST_F(OomdContextTest, HasCgroupCheck) { CgroupPath path("/sys/fs/cgroup", "asdf"); EXPECT_FALSE(ctx.hasCgroupContext(path)); ctx.setCgroupContext(path, CgroupContext{}); EXPECT_TRUE(ctx.hasCgroupContext(path)); } TEST_F(OomdContextTest, CgroupKeys) { CgroupPath p1("/sys/fs/cgroup", "asdf"); CgroupPath p2("/sys/fs/cgroup", "wow"); EXPECT_EQ(ctx.cgroups().size(), 0); ctx.setCgroupContext(p1, CgroupContext{}); ctx.setCgroupContext(p2, CgroupContext{}); EXPECT_EQ(ctx.cgroups().size(), 2); EXPECT_THAT(ctx.cgroups(), Contains(p1)); EXPECT_THAT(ctx.cgroups(), Contains(p2)); } TEST_F(OomdContextTest, CgroupKeyRoot) { CgroupPath root("/sys/fs/cgroup", "/"); ctx.setCgroupContext(root, CgroupContext{.current_usage = 12345}); EXPECT_EQ(ctx.cgroups().size(), 1); EXPECT_EQ(ctx.getCgroupContext(root).current_usage, 12345); } TEST_F(OomdContextTest, SetCgroup) { CgroupPath p1("/sys/fs/cgroup", "asdf"); ctx.setCgroupContext(p1, CgroupContext{}); EXPECT_EQ(ctx.getCgroupContext(p1).memory_low, 0); ctx.setCgroupContext(p1, CgroupContext{{}, {}, 0, 0, 222}); EXPECT_EQ(ctx.getCgroupContext(p1).memory_low, 222); CgroupPath p2("/sys/fs/cgroup", "A/B/C"); ctx.setCgroupContext(p2, CgroupContext{}); CgroupPath p2_parent = p2.getParent(); CgroupPath p2_parent_parent = p2_parent.getParent(); EXPECT_TRUE(ctx.hasCgroupContext(p2)); EXPECT_FALSE(ctx.hasCgroupContext(p2_parent)); EXPECT_FALSE(ctx.hasCgroupContext(p2_parent_parent)); } TEST_F(OomdContextTest, DeepNesting) { CgroupPath p1("/sys/fs/cgroup", "A"); CgroupPath p2("/sys/fs/cgroup", "A/B"); CgroupPath p3("/sys/fs/cgroup", "A/B/C"); CgroupPath p4("/sys/fs/cgroup", "A/B/D"); CgroupPath p5("/sys/fs/cgroup", "A/B/D/E"); ctx.setCgroupContext(p1, CgroupContext{{}, {}, 1}); ctx.setCgroupContext(p2, CgroupContext{{}, {}, 2}); ctx.setCgroupContext(p3, CgroupContext{{}, {}, 3}); ctx.setCgroupContext(p4, CgroupContext{{}, {}, 4}); ctx.setCgroupContext(p5, CgroupContext{{}, {}, 5}); EXPECT_TRUE(ctx.hasCgroupContext(p1)); EXPECT_TRUE(ctx.hasCgroupContext(p2)); EXPECT_TRUE(ctx.hasCgroupContext(p3)); EXPECT_TRUE(ctx.hasCgroupContext(p4)); EXPECT_TRUE(ctx.hasCgroupContext(p5)); EXPECT_EQ(ctx.getCgroupContext(p1).current_usage, 1); EXPECT_EQ(ctx.getCgroupContext(p2).current_usage, 2); EXPECT_EQ(ctx.getCgroupContext(p3).current_usage, 3); EXPECT_EQ(ctx.getCgroupContext(p4).current_usage, 4); EXPECT_EQ(ctx.getCgroupContext(p5).current_usage, 5); } TEST_F(OomdContextTest, TreeWalk) { CgroupPath p1("/sys/fs/cgroup", "A"); CgroupPath p2("/sys/fs/cgroup", "A/B"); CgroupPath p3("/sys/fs/cgroup", "A/B/C"); CgroupPath p4("/sys/fs/cgroup", "A/B/C/D"); ctx.setCgroupContext(p1, CgroupContext{}); ctx.setCgroupContext(p2, CgroupContext{}); ctx.setCgroupContext(p3, CgroupContext{}); ctx.setCgroupContext(p4, CgroupContext{}); auto pp4 = ctx.getCgroupNode(p4); ASSERT_TRUE(pp4->parent.lock()); EXPECT_EQ(pp4->parent.lock()->path, p3); ASSERT_TRUE(pp4->parent.lock()->parent.lock()); EXPECT_EQ(pp4->parent.lock()->parent.lock()->path, p2); ASSERT_TRUE(pp4->parent.lock()->parent.lock()->parent.lock()); EXPECT_EQ(pp4->parent.lock()->parent.lock()->parent.lock()->path, p1); ASSERT_TRUE(pp4->parent.lock()->parent.lock()->parent.lock()->parent.lock()); EXPECT_TRUE(pp4->parent.lock() ->parent.lock() ->parent.lock() ->parent.lock() ->path.isRoot()); } TEST_F(OomdContextTest, SortContext) { CgroupPath p1("/sys/fs/cgroup", "biggest"); CgroupPath p2("/sys/fs/cgroup", "smallest"); CgroupPath p3("/sys/fs/cgroup", "asdf"); CgroupPath p4("/sys/fs/cgroup", "fdsa"); ctx.setCgroupContext(p1, {{}, {}, 99999999, 0, 1}); ctx.setCgroupContext(p2, {{}, {}, 1, 988888888888, 4}); ctx.setCgroupContext(p3, {{}, {}, 88888888, 289349817823, 2}); ctx.setCgroupContext(p4, {{}, {}, 77777777, 6, 3}); auto sorted = ctx.reverseSort( [](const CgroupContext& cgroup_ctx) { return cgroup_ctx.current_usage; }); ASSERT_EQ(sorted.size(), 4); EXPECT_EQ(sorted.at(0).first, p1); EXPECT_EQ(sorted.at(3).first, p2); OomdContext::reverseSort(sorted, [](const CgroupContext& cgroup_ctx) { return cgroup_ctx.memory_low; }); ASSERT_EQ(sorted.size(), 4); EXPECT_EQ(sorted.at(0).first, p2); EXPECT_EQ(sorted.at(3).first, p1); } oomd-0.3.1/src/oomd/OomdTest.cpp000066400000000000000000000232451362162667600164650ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "oomd/Log.h" #include "oomd/Oomd.h" #include "oomd/util/Fs.h" using namespace Oomd; using namespace testing; // TODO: Use std::filesystem once it doesn't require header/ifdef/linker // shenanigans to reliably work on modernish GCC/Clang std::string _get_working_path() { // This goes to a C API, don't complain about not using std::array char buf[PATH_MAX]; // @nolint char* res; res = ::getcwd(buf, sizeof(buf)); if (!res) { throw std::runtime_error("Can't getcwd"); } return buf; } const auto kCgroupDataDir = _get_working_path() + "/oomd/fixtures/cgroup"; class OomdTest : public ::testing::Test { public: OomdTest() { oomd = std::make_unique<::Oomd::Oomd>( nullptr, nullptr, 5, kCgroupDataDir, "", io_devs, hdd_coeffs, ssd_coeffs); } std::string cgroup_path{kCgroupDataDir}; OomdContext ctx; std::unique_ptr<::Oomd::Oomd> oomd{nullptr}; CgroupPath service1{cgroup_path, "system.slice/service1.service"}; CgroupPath service2{cgroup_path, "system.slice/service2.service"}; CgroupPath service3{cgroup_path, "system.slice/service3.service"}; CgroupPath service4{cgroup_path, "system.slice/service4.service"}; CgroupPath slice1{cgroup_path, "system.slice/slice1.slice"}; CgroupPath workload_service1{cgroup_path, "workload.slice/service1.service"}; std::unordered_map io_devs = { {"1:11", DeviceType::SSD}}; IOCostCoeffs hdd_coeffs = { .read_iops = 6, .readbw = 5, .write_iops = 4, .writebw = 3, .trim_iops = 2, .trimbw = 1, }; IOCostCoeffs ssd_coeffs = { .read_iops = 1, .readbw = 2, .write_iops = 3, .writebw = 4, .trim_iops = 5, .trimbw = 6, }; }; TEST_F(OomdTest, OomdContextUpdate) { EXPECT_EQ(ctx.cgroups().size(), 0); std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "system.slice/*")); oomd->updateContext(cgroups, ctx); EXPECT_EQ(ctx.cgroups().size(), 5); EXPECT_TRUE(ctx.hasCgroupContext(service1)); EXPECT_TRUE(ctx.hasCgroupContext(service2)); EXPECT_TRUE(ctx.hasCgroupContext(service3)); EXPECT_TRUE(ctx.hasCgroupContext(service4)); EXPECT_TRUE(ctx.hasCgroupContext(slice1)); } TEST_F(OomdTest, OomdContextMultipleUpdates) { std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "system.slice/*")); oomd->updateContext(cgroups, ctx); for (int i = 0; i < 3; i++) { int64_t average = ctx.getCgroupContext(service1).average_usage; oomd->updateContext(cgroups, ctx); // We expect the avg usage to slowly converge from 0 -> true avg // b/c of AVERAGE_SIZE_DECAY EXPECT_GT(ctx.getCgroupContext(service1).average_usage, average); } } TEST_F(OomdTest, OomdContextUpdateMultiCgroup) { EXPECT_EQ(ctx.cgroups().size(), 0); std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "system.slice/*")); cgroups.emplace(CgroupPath(cgroup_path, "workload.slice/*")); oomd->updateContext(cgroups, ctx); EXPECT_EQ(ctx.cgroups().size(), 6); EXPECT_TRUE(ctx.hasCgroupContext(service1)); EXPECT_TRUE(ctx.hasCgroupContext(service2)); EXPECT_TRUE(ctx.hasCgroupContext(service3)); EXPECT_TRUE(ctx.hasCgroupContext(service4)); EXPECT_TRUE(ctx.hasCgroupContext(slice1)); EXPECT_TRUE(ctx.hasCgroupContext(workload_service1)); } TEST_F(OomdTest, OomdContextUpdateMultiCgroupWildcard) { EXPECT_EQ(ctx.cgroups().size(), 0); std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "*.slice/*")); cgroups.emplace(CgroupPath(cgroup_path, "workload.slice/*")); oomd->updateContext(cgroups, ctx); EXPECT_EQ(ctx.cgroups().size(), 6); EXPECT_TRUE(ctx.hasCgroupContext(service1)); EXPECT_TRUE(ctx.hasCgroupContext(service2)); EXPECT_TRUE(ctx.hasCgroupContext(service3)); EXPECT_TRUE(ctx.hasCgroupContext(service4)); EXPECT_TRUE(ctx.hasCgroupContext(slice1)); EXPECT_TRUE(ctx.hasCgroupContext(workload_service1)); } TEST_F(OomdTest, CalculateProtectionOverage) { std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "system.slice/*")); oomd->updateContext(cgroups, ctx); auto s1_ctx = ctx.getCgroupContext(service1); auto s2_ctx = ctx.getCgroupContext(service2); auto s3_ctx = ctx.getCgroupContext(service3); auto s4_ctx = ctx.getCgroupContext(service4); auto sl1_ctx = ctx.getCgroupContext(slice1); EXPECT_LT(s1_ctx.effective_usage(), s2_ctx.effective_usage()); EXPECT_LT(s1_ctx.effective_usage(), s3_ctx.effective_usage()); EXPECT_LT(s1_ctx.effective_usage(), s4_ctx.effective_usage()); EXPECT_LT(s1_ctx.effective_usage(), sl1_ctx.effective_usage()); EXPECT_EQ(s2_ctx.effective_usage(), s3_ctx.effective_usage()); EXPECT_EQ(s2_ctx.effective_usage(), s4_ctx.effective_usage()); EXPECT_EQ(s2_ctx.effective_usage(), sl1_ctx.effective_usage()); } TEST_F(OomdTest, MonitorRootHost) { std::string cgroup2fs_mntpt = Fs::getCgroup2MountPoint(); if (cgroup2fs_mntpt.empty()) { #ifdef GTEST_SKIP GTEST_SKIP() << "Host not running cgroup2"; #else // GTEST_SKIP() is in gtest 1.9.x (note: starting at 1.9.x, // gtest is living at master) and it's unlikely that it will // ever get packaged. OLOG << "Host not running cgroup2"; return; #endif } if (cgroup2fs_mntpt == "/sys/fs/cgroup/unified/") { // If we have the cgroup tree in "hybrid" mode, this will fail when we test // to get the cgroup context, since we will not find any memory stat files // in that tree. Let's also skip this test in that case. // TODO: A better approach would be to find which controllers are mounted in // the cgroup2 mountpoint, but for now this should be enough to get us to // pass our tests on Travis-CI. #ifdef GTEST_SKIP GTEST_SKIP() << "This test does not support hybrid hierarchy"; #else OLOG << "This test does not support hybrid hierarchy"; return; #endif } std::unordered_set cgroups; CgroupPath root(cgroup2fs_mntpt, "/"); cgroups.emplace(root); oomd->updateContext(cgroups, ctx); int64_t current = ctx.getCgroupContext(root).current_usage; // If we're running the test, I should hope the root host is using memory // otherwise we've really stumbled onto a competitive advantage. EXPECT_GT(current, 0); } TEST_F(OomdTest, CalculateProtectionOverageContrived) { const std::string contrived_cgroup_path = cgroup_path + "/protection_overage.fakeroot"; std::unordered_set cgroups; cgroups.emplace(CgroupPath(contrived_cgroup_path, "*/*")); // We're manually adding in ancestors here b/c Oomd::Oomd usually does // this for us and we're not using the real constructor code path cgroups.emplace(CgroupPath(contrived_cgroup_path, "*")); oomd->updateContext(cgroups, ctx); ctx.dump(); int64_t A = ctx.getCgroupContext(CgroupPath(contrived_cgroup_path, "A")) .effective_usage(); int64_t A1 = ctx.getCgroupContext(CgroupPath(contrived_cgroup_path, "A/A1")) .effective_usage(); int64_t A2 = ctx.getCgroupContext(CgroupPath(contrived_cgroup_path, "A/A2")) .effective_usage(); int64_t B = ctx.getCgroupContext(CgroupPath(contrived_cgroup_path, "B")) .effective_usage(); int64_t B1 = ctx.getCgroupContext(CgroupPath(contrived_cgroup_path, "B/B1")) .effective_usage(); int64_t B2 = ctx.getCgroupContext(CgroupPath(contrived_cgroup_path, "B/B2")) .effective_usage(); EXPECT_EQ(A, 2ll << 30); EXPECT_EQ(B, 3ll << 30); // Hierarchy is B1 > B2 >= A1 > A2 EXPECT_GT(A1, A2); EXPECT_EQ(B2, A1); EXPECT_GT(B1, B2); } TEST_F(OomdTest, CalculateIOCostCumulative) { /* system.slice/io.stat content: 1:10 rbytes=1111111 wbytes=2222222 rios=33 wios=44 dbytes=5555555555 dios=6 1:11 rbytes=2222222 wbytes=3333333 rios=44 wios=55 dbytes=6666666666 dios=7 */ std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "system.slice")); oomd->updateContext(cgroups, ctx); auto expected_io_cost_cum = 2222222 /*rbytes*/ * ssd_coeffs.readbw + 3333333 /*wbytes*/ * ssd_coeffs.writebw + 44 /*rios*/ * ssd_coeffs.read_iops + 55 /*wios*/ * ssd_coeffs.write_iops + 6666666666 /*dbytes*/ * ssd_coeffs.trimbw + 7 /*dios*/ * ssd_coeffs.trim_iops; auto root_ctx = ctx.getCgroupContext(CgroupPath(cgroup_path, "system.slice")); EXPECT_EQ(root_ctx.io_cost_cumulative, expected_io_cost_cum); EXPECT_EQ(root_ctx.io_cost_rate, 0); // initial rate should be zero oomd->updateContext(cgroups, ctx); // if file is not change, nothing should change EXPECT_EQ(root_ctx.io_cost_cumulative, expected_io_cost_cum); EXPECT_EQ(root_ctx.io_cost_rate, 0); } TEST_F(OomdTest, MissingControlFiles) { std::unordered_set cgroups; cgroups.emplace(CgroupPath(cgroup_path, "missing_control_files.slice")); ASSERT_NO_THROW(oomd->updateContext(cgroups, ctx)); EXPECT_EQ(ctx.cgroups().size(), 0); } oomd-0.3.1/src/oomd/PluginRegistry.cpp000066400000000000000000000016201362162667600177070ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "PluginRegistry.h" namespace Oomd { PluginRegistry& getPluginRegistry() { static PluginRegistry r; return r; } } // namespace Oomd oomd-0.3.1/src/oomd/PluginRegistry.h000066400000000000000000000034651362162667600173650ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include "oomd/engine/BasePlugin.h" namespace Oomd { template class PluginRegistry { public: using FactoryFunction = std::function; using FactoryMap = std::unordered_map; bool add(const std::string& name, FactoryFunction fac) { if (map_.find(name) != map_.end()) { return false; } map_[name] = fac; return true; } T* create(const std::string& name) { if (map_.find(name) == map_.end()) { return nullptr; } return map_[name](); } std::vector getRegistered() const { std::vector list_plugins; for (auto const& data : map_) { list_plugins.push_back(data.first); } return list_plugins; } private: FactoryMap map_; }; PluginRegistry& getPluginRegistry(); #define REGISTER_PLUGIN(plugin_name, create_func) \ bool plugin_name##_plugin_entry = \ getPluginRegistry().add(#plugin_name, (create_func)) } // namespace Oomd oomd-0.3.1/src/oomd/Stats.cpp000066400000000000000000000215531362162667600160250ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "oomd/Stats.h" #include "oomd/StatsClient.h" #include "oomd/include/Assert.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" namespace Oomd { Stats::Stats(const std::string& stats_socket_path) : stats_socket_path_(stats_socket_path) { if (!this->startSocket()) { throw std::runtime_error("Socket thread failed to start"); } } Stats::~Stats() { std::array err_buf = {}; statsThreadRunning_ = false; auto client = StatsClient(stats_socket_path_); client.closeSocket(); std::unique_lock lock(thread_mutex_); if (!thread_exited_.wait_for(lock, std::chrono::seconds(5), [this] { return this->thread_count_ == 0; })) { OLOG << "Closing stats error: timed out waiting for a thread"; // Crash here because client threads _must_ timeout in < 5s. // If we don't crash then we risk the client threads accessing destructed // members. Note this should never happen. OCHECK(false); } lock.unlock(); if (stats_thread_.joinable()) { stats_thread_.join(); } if (::unlink(serv_addr_.sun_path) < 0) { OLOG << "Closing stats error: unlinking socket path: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); } if (::close(sockfd_) < 0) { OLOG << "Closing stats error: closing stats socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); } } Stats& Stats::get(const std::string& stats_socket_path) { static Stats singleton(stats_socket_path); return singleton; } std::unique_ptr Stats::get_for_unittest( const std::string& stats_socket_path) { return std::unique_ptr(new Stats(stats_socket_path)); } bool Stats::init(const std::string& stats_socket_path) { try { Stats::get(stats_socket_path); } catch (const std::runtime_error& e) { OLOG << "Initializing singleton failed: " << e.what(); return false; } isInitInternal() = true; return true; } bool Stats::isInit() { return isInitInternal(); } bool& Stats::isInitInternal() { static bool init = false; return init; } bool Stats::startSocket() { std::array err_buf = {}; size_t dir_end = stats_socket_path_.find_last_of("/"); // Iteratively checks if dirs in path exist, creates them if not if (dir_end != std::string::npos) { std::string dirs = stats_socket_path_.substr(0, dir_end); size_t pos = 0; if (dirs[0] == '/') { pos++; } while (pos < dirs.size()) { pos = dirs.find('/', pos); if (pos == std::string::npos) { pos = dir_end; } std::string curr_path = dirs.substr(0, pos); int res = ::mkdir(curr_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); if (res != 0 && errno != EEXIST) { OLOG << "Making socket dir error: " << ::strerror_r(errno, err_buf.data(), err_buf.size()) << ": " << curr_path; return false; } pos++; } } sockfd_ = ::socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd_ < 0) { OLOG << "Error creating socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size() - 1); return false; } ::memset(&serv_addr_, '\0', sizeof(serv_addr_)); serv_addr_.sun_family = AF_UNIX; ::strcpy(serv_addr_.sun_path, stats_socket_path_.c_str()); if (::unlink(serv_addr_.sun_path) < 0 && errno != ENOENT) { OLOG << "Pre-unlinking of socket path failed. " << serv_addr_.sun_path << ". Errno: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); return false; } if (::bind(sockfd_, (struct sockaddr*)&serv_addr_, sizeof(serv_addr_)) < 0) { OLOG << "Error binding stats collection socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); return false; } if (::chmod(stats_socket_path_.c_str(), 0666) < 0) { OLOG << "Unable to set permissions on " << stats_socket_path_; return false; } if (::listen(sockfd_, 5) < 0) { OLOG << "Error listening at socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); return false; } stats_thread_ = std::thread([this] { this->runSocket(); }); return true; } void Stats::runSocket() { sockaddr_un cli_addr; socklen_t clilen = sizeof(cli_addr); std::array err_buf = {}; while (statsThreadRunning_) { int sockfd = ::accept(sockfd_, (struct sockaddr*)&cli_addr, &clilen); if (sockfd < 0) { OLOG << "Stats server error: accepting connection: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); continue; } const timeval io_timeout{.tv_sec = 2, .tv_usec = 0}; const void* time_ptr = static_cast(&io_timeout); ::setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, time_ptr, sizeof io_timeout); ::setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, time_ptr, sizeof io_timeout); std::unique_lock lock(thread_mutex_); ++thread_count_; std::thread msg_thread_ = std::thread([this, sockfd] { this->processMsg(sockfd); }); msg_thread_.detach(); lock.unlock(); thread_exited_.notify_one(); } } void Stats::processMsg(int sockfd) { std::array err_buf = {}; OOMD_SCOPE_EXIT { if (::close(sockfd) < 0) { OLOG << "Stats server error: closing file descriptor: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); } }; char mode = 'a'; char byte_buf; int num_read = 0; for (; num_read < 32; num_read++) { int res = ::read(sockfd, &byte_buf, 1); if (res < 0) { // Error reading OLOG << "Stats server error: reading from socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); return; } else if (res == 0) { // EOF reached break; } // We read a char if (byte_buf == '\n' || byte_buf == '\0') { break; } if (num_read == 0) { // We only care about the first char mode = byte_buf; } } if (num_read == 0) { OLOG << "Stats server error: no msg received"; } Json::Value root; root["error"] = 0; Json::Value body(Json::objectValue); switch (mode) { case 'g': for (auto const& pair : getAll()) { body[pair.first] = pair.second; } break; case 'r': Stats::reset(); break; case '0': break; default: root["error"] = 1; OLOG << "Stats server error: received unknown request: " << mode; } root["body"] = body; std::string ret = root.toStyledString(); if (Util::writeFull(sockfd, ret.c_str(), strlen(ret.c_str())) < 0) { OLOG << "Stats server error: writing to socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()); } std::unique_lock lock(thread_mutex_); thread_count_--; lock.unlock(); thread_exited_.notify_one(); } std::unordered_map Stats::getAll() { std::lock_guard lock(stats_mutex_); return stats_; } int Stats::increment(const std::string& key, int val) { std::lock_guard lock(stats_mutex_); stats_[key] = stats_[key] + val; return 0; } int Stats::set(const std::string& key, int val) { std::lock_guard lock(stats_mutex_); stats_[key] = val; return 0; } int Stats::reset() { std::lock_guard lock(stats_mutex_); for (const auto& pair : stats_) { stats_[pair.first] = 0; } return 0; } std::unordered_map getStats() { if (!Stats::isInit()) { OLOG << "Warning: stats module not initialized"; return {}; } return Stats::get().getAll(); } int incrementStat(const std::string& key, int val) { if (!Stats::isInit()) { OLOG << "Warning: stats module not initialized"; return 1; } return Stats::get().increment(key, val); } int setStat(const std::string& key, int val) { if (!Stats::isInit()) { OLOG << "Warning: stats module not initialized"; return 1; } return Stats::get().set(key, val); } int resetStats() { if (!Stats::isInit()) { OLOG << "Warning: stats module not initialized"; return 1; } return Stats::get().reset(); } } // namespace Oomd oomd-0.3.1/src/oomd/Stats.h000066400000000000000000000053671362162667600154770ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include "oomd/Log.h" namespace Oomd { class Stats { public: Stats(const Stats& other) = delete; Stats& operator=(const Stats& other) = delete; ~Stats(); /* * Initializes stats singleton */ static bool init(const std::string& stats_socket_path); static bool isInit(); /* * Returns stats singleton */ static Stats& get(const std::string& stats_socket_path = ""); /* * Returns ptr to Stats object for unit tests */ static std::unique_ptr get_for_unittest( const std::string& stats_socket_path); /* * Returns a copy of current stats */ std::unordered_map getAll(); /* * Increments designated key in stats by passed val. * If key is not present, initializes key to val. */ int increment(const std::string& key, int val); /* * Sets designated key to equal val in stats collection */ int set(const std::string& key, int val); /* * Sets all existing values in stats to 0 */ int reset(); private: static bool& isInitInternal(); /* * Ensure only get() can construct */ explicit Stats(const std::string& stats_socket_path); // Opens socket for incoming stats queries bool startSocket(); /* * Accepts new connections to socket and delegates * created fds to processMsg() */ void runSocket(); /* * Processes a msg on a given fd */ void processMsg(int sockfd); // Notifies the stats socket thread to stop std::atomic statsThreadRunning_{true}; std::mutex stats_mutex_; std::string stats_socket_path_; sockaddr_un serv_addr_; int sockfd_; std::unordered_map stats_; std::thread stats_thread_; std::mutex thread_mutex_; std::atomic thread_count_{0}; std::condition_variable thread_exited_; }; /* * Public API */ std::unordered_map getStats(); int incrementStat(const std::string& key, int val); int setStat(const std::string& key, int val); int resetStats(); } // namespace Oomd oomd-0.3.1/src/oomd/StatsClient.cpp000066400000000000000000000117241362162667600171630ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/StatsClient.h" #include #include #include #include #include #include #include #include #include "oomd/Stats.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" namespace { std::optional parseJson(const std::string& input) { Json::Value ret; std::string errs; Json::CharReaderBuilder rbuilder; std::istringstream sinput(input); bool ok = Json::parseFromStream(rbuilder, sinput, &ret, &errs); if (!ok) { std::cerr << "Unable to parse JSON: " << errs; return std::nullopt; } return ret; } } // namespace namespace Oomd { StatsClient::StatsClient(const std::string& stats_socket_path) : stats_socket_path_(stats_socket_path) { serv_addr_.sun_family = AF_UNIX; ::strcpy(serv_addr_.sun_path, stats_socket_path_.c_str()); } std::optional> StatsClient::getStats() { const auto msg = msgSocket("g"); if (!msg) { return std::nullopt; } auto root = parseJson(*msg); if (!root) { return std::nullopt; } try { if ((*root)["error"].asInt()) { std::cerr << "StatsClient error: received error code=" << (*root)["error"].toStyledString() << std::endl; return std::nullopt; } } catch (const std::exception& e) { std::cerr << "StatsClient error: parsed error value not an int" << std::endl; return std::nullopt; } const auto& body = (*root)["body"]; std::unordered_map ret_map; for (const auto& key : body.getMemberNames()) { ret_map[key] = body[key].asInt(); } return ret_map; } int StatsClient::resetStats() { const auto msg = msgSocket("r"); if (!msg) { return 1; } auto root = parseJson(*msg); if (!root) { return 1; } try { return (*root)["error"].asInt(); } catch (const std::exception& e) { std::cerr << "StatsClient error: parsed error value not an int" << std::endl; return 1; } } int StatsClient::closeSocket() { const auto msg = msgSocket("0"); if (!msg) { return 1; } auto root = parseJson(*msg); if (!root) { return 1; } try { return (*root)["error"].asInt(); } catch (const std::exception& e) { std::cerr << "StatsClient error: parsed error value not an int" << std::endl; return 1; } } std::optional StatsClient::msgSocket(std::string msg) { std::array err_buf = {}; int sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "Error: creating client socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()) << std::endl; return std::nullopt; } OOMD_SCOPE_EXIT { if (::close(sockfd) < 0) { std::cerr << "Error: shutting down client socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()) << std::endl; } }; const timeval io_timeout{.tv_sec = 2, .tv_usec = 0}; const void* time_ptr = static_cast(&io_timeout); ::setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, time_ptr, sizeof io_timeout); ::setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, time_ptr, sizeof io_timeout); if (::connect(sockfd, (struct sockaddr*)&serv_addr_, sizeof(serv_addr_)) < 0) { std::cerr << "Error: connecting to stats socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()) << "\nSocket path: " << serv_addr_.sun_path << std::endl; return std::nullopt; } msg += '\n'; if (Util::writeFull(sockfd, msg.c_str(), strlen(msg.c_str())) < 0) { std::cerr << "Error: writing to stats socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()) << std::endl; return std::nullopt; } std::string ret = ""; std::array msg_buf = {}; while (true) { int n = Util::readFull(sockfd, msg_buf.data(), msg_buf.size() - 1); if (n < 0) { std::cerr << "Error: reading from stats socket: " << ::strerror_r(errno, err_buf.data(), err_buf.size()) << std::endl; return std::nullopt; } else if (n == 0) { break; } msg_buf[n] = '\0'; ret += std::string(msg_buf.data()); } return ret; } } // namespace Oomd oomd-0.3.1/src/oomd/StatsClient.h000066400000000000000000000027121362162667600166250ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include namespace Oomd { class StatsClient { public: explicit StatsClient(const std::string& stats_socket_path); /* * Connect to socket and return the current stats */ std::optional> getStats(); /* * Connect to socket and reset the current stats */ int resetStats(); /* * Connect to socket and send a closing message */ int closeSocket(); /* * Send a message to the socket. Appends a \n delimiter to the message * before sending. Returns the the socket's response. */ std::optional msgSocket(std::string msg); private: std::string stats_socket_path_; sockaddr_un serv_addr_; }; } // namespace Oomd oomd-0.3.1/src/oomd/StatsTest.cpp000066400000000000000000000153441362162667600166660ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/Stats.h" #include #include #include #include #include #include #include "oomd/StatsClient.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" using namespace Oomd; using namespace testing; namespace { std::optional parseJson(const std::string& input) { Json::Value ret; std::string errs; Json::CharReaderBuilder rbuilder; std::istringstream sinput(input); bool ok = Json::parseFromStream(rbuilder, sinput, &ret, &errs); if (!ok) { std::cerr << "Unable to parse JSON: " << errs; return std::nullopt; } return ret; } } // namespace class StatsTest : public ::testing::Test { public: std::string socket_path; protected: std::unique_ptr get_instance() { socket_path = "/tmp/oomd-XXXXXX.socket"; ::mkstemps(socket_path.data(), 7); return Stats::get_for_unittest(socket_path); } }; TEST_F(StatsTest, BasicStats) { auto stats = get_instance(); ASSERT_TRUE(stats); EXPECT_EQ(stats->getAll().size(), 0); // Increment and check EXPECT_EQ(stats->increment("one", 1), 0); EXPECT_EQ(stats->increment("two", 2), 0); EXPECT_EQ(stats->getAll().size(), 2); // Clear and check vals EXPECT_EQ(stats->reset(), 0); auto map = stats->getAll(); EXPECT_EQ(map.size(), 2); for (auto& pair : map) { EXPECT_EQ(pair.second, 0); } // Increment with existing keys EXPECT_EQ(stats->increment("one", 1), 0); EXPECT_EQ(stats->set("two", 2), 0); EXPECT_EQ(stats->increment("two", 2), 0); map = stats->getAll(); EXPECT_EQ(map.size(), 2); EXPECT_EQ(map["one"], 1); EXPECT_EQ(map["two"], 2 * 2); } TEST_F(StatsTest, InvalidSocketPath) { std::string invalid_path = "/var/"; EXPECT_THROW(Oomd::Stats::get_for_unittest(invalid_path), std::runtime_error); } TEST_F(StatsTest, InvalidRequests) { auto stats_ptr = get_instance(); ASSERT_NE(stats_ptr, nullptr); auto client = StatsClient(socket_path); { const std::string msg = "g"; auto msg_opt = client.msgSocket(msg); ASSERT_TRUE(msg_opt); auto root = parseJson(*msg_opt); ASSERT_TRUE(root); EXPECT_EQ((*root)["error"], 0); } { const std::string msg = "xlskdjfksdj"; auto msg_opt = client.msgSocket(msg); ASSERT_TRUE(msg_opt); auto root = parseJson(*msg_opt); ASSERT_TRUE(root); EXPECT_EQ((*root)["error"], 1); } { const std::string msg = "ysdf\nasdf"; auto msg_opt = client.msgSocket(msg); ASSERT_FALSE(msg_opt); } { const std::string msg = "zsdfasdfasdfasdfasdfasdfasdfadsfasdfasdf"; auto msg_opt = client.msgSocket(msg); ASSERT_FALSE(msg_opt); } { const std::string msg = ""; auto msg_opt = client.msgSocket(msg); ASSERT_TRUE(msg_opt); auto root = parseJson(*msg_opt); ASSERT_TRUE(root); EXPECT_EQ((*root)["error"], 1); } } TEST_F(StatsTest, BasicGetAndClear) { auto stats = get_instance(); auto client = StatsClient(socket_path); EXPECT_EQ(client.getStats()->size(), 0); int limit = 3; { // Stats increments, Client Gets and checks for (int i = 0; i < limit; i++) { EXPECT_EQ(stats->increment(std::to_string(i), i), 0); } EXPECT_EQ(stats->getAll().size(), limit); auto client_map_ptr = client.getStats(); ASSERT_TRUE(client_map_ptr); auto& client_map = *client_map_ptr; EXPECT_EQ(client_map.size(), limit); for (int i = 0; i < limit; i++) { EXPECT_EQ(client_map[std::to_string(i)], i); } } { // Client Clears, Stats/Client Gets and checks EXPECT_EQ(client.resetStats(), 0); auto stats_map = stats->getAll(); EXPECT_EQ(stats_map.size(), limit); for (int i = 0; i < limit; i++) { EXPECT_EQ(stats_map.at(std::to_string(i)), 0); } auto client_map_ptr = client.getStats(); ASSERT_TRUE(client_map_ptr); auto& client_map = *client_map_ptr; EXPECT_EQ(client_map.size(), limit); for (int i = 0; i < limit; i++) { EXPECT_EQ(stats_map.at(std::to_string(i)), 0); } } } TEST_F(StatsTest, LongJson) { auto stats = get_instance(); auto client = StatsClient(socket_path); EXPECT_EQ(client.getStats()->size(), 0); int limit = 10000; { // Stats increments, Client Gets and checks for (int i = 0; i < limit; i++) { EXPECT_EQ(stats->increment(std::to_string(i), i), 0); } EXPECT_EQ(stats->getAll().size(), limit); auto client_map_ptr = client.getStats(); ASSERT_TRUE(client_map_ptr); auto& client_map = *client_map_ptr; EXPECT_EQ(client_map.size(), limit); for (int i = 0; i < limit; i++) { EXPECT_EQ(client_map[std::to_string(i)], i); } } { // Client Clears, Stats/Client Gets and checks EXPECT_EQ(client.resetStats(), 0); auto stats_map = stats->getAll(); EXPECT_EQ(stats_map.size(), limit); for (int i = 0; i < limit; i++) { EXPECT_EQ(stats_map.at(std::to_string(i)), 0); } auto client_map_ptr = client.getStats(); ASSERT_TRUE(client_map_ptr); auto& client_map = *client_map_ptr; EXPECT_EQ(client_map.size(), limit); for (int i = 0; i < limit; i++) { EXPECT_EQ(stats_map.at(std::to_string(i)), 0); } } } TEST_F(StatsTest, MultiThreadedMany) { sockaddr_un serv_addr_; auto stats = get_instance(); serv_addr_.sun_family = AF_UNIX; ::strcpy(serv_addr_.sun_path, socket_path.c_str()); const int num_threads = 50; std::array sockfds; for (int i = 0; i < num_threads; i++) { int sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); sockfds[i] = sockfd; EXPECT_GE(sockfd, 0); int connected = ::connect(sockfd, (struct sockaddr*)&serv_addr_, sizeof(serv_addr_)); EXPECT_GE(connected, 0); std::string msg = "g\n"; int res = Util::writeFull(sockfd, msg.c_str(), strlen(msg.c_str())); EXPECT_GE(res, 0); } // server threads are now blocked on write() for (int i = 0; i < num_threads; i++) { std::array msg_buf = {}; int res = Util::readFull(sockfds[i], msg_buf.data(), msg_buf.size() - 1); EXPECT_GE(res, 0); EXPECT_EQ(::close(sockfds[i]), 0); } } oomd-0.3.1/src/oomd/config/000077500000000000000000000000001362162667600154625ustar00rootroot00000000000000oomd-0.3.1/src/oomd/config/ConfigCompiler.cpp000066400000000000000000000137241362162667600210750ustar00rootroot00000000000000/* Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/config/ConfigCompiler.h" #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/engine/BasePlugin.h" #include "oomd/engine/DetectorGroup.h" #include "oomd/engine/EngineTypes.h" #include "oomd/engine/Ruleset.h" #include "oomd/util/Util.h" namespace { template std::unique_ptr compilePlugin( Oomd::Engine::MonitoredResources& resources, const T& plugin) { if (plugin.name.empty()) { OLOG << "Plugin is missing name"; return nullptr; } std::unique_ptr instance( Oomd::getPluginRegistry().create(plugin.name)); if (!instance) { OLOG << "Could not locate plugin=" << plugin.name << " in plugin registry"; return nullptr; } instance->setName(plugin.name); int ret = instance->init(resources, plugin.args); if (ret != 0) { OLOG << "Plugin=" << plugin.name << " failed to init() with code=" << ret; return nullptr; } return instance; } std::unique_ptr compileDetectorGroup( Oomd::Engine::MonitoredResources& resources, const Oomd::Config2::IR::DetectorGroup& group) { std::vector> detectors; if (group.name.empty()) { OLOG << "DetectorGroup is missing name"; return nullptr; } if (group.detectors.empty()) { OLOG << "DetectorGroup is missing Detectors"; return nullptr; } for (const auto& detector : group.detectors) { auto compiled_plugin = compilePlugin(resources, detector); if (!compiled_plugin) { return nullptr; } detectors.emplace_back(std::move(compiled_plugin)); } return std::make_unique( group.name, std::move(detectors)); } std::unique_ptr compileRuleset( Oomd::Engine::MonitoredResources& resources, const Oomd::Config2::IR::Ruleset& ruleset, bool dropin) { uint32_t silenced_logs = 0; std::vector> detector_groups; std::vector> actions; if (ruleset.name.empty()) { OLOG << "Ruleset is missing name"; return nullptr; } // Log silencing field is optional if (ruleset.silence_logs.size()) { auto copy = ruleset.silence_logs; Oomd::Util::trim(copy); auto parts = Oomd::Util::split(copy, ','); for (auto& part : parts) { Oomd::Util::trim(part); if (part == "engine") { silenced_logs |= Oomd::Engine::LogSources::ENGINE; } else if (part == "plugins") { silenced_logs |= Oomd::Engine::LogSources::PLUGINS; } else { OLOG << "Unrecognized log source=" << part; return nullptr; } } } if (!dropin && (ruleset.dgs.empty() || ruleset.acts.empty())) { OLOG << "Ruleset is missing DetectorGroups or missing Actions"; return nullptr; } for (const auto& dg : ruleset.dgs) { auto compiled_detectorgroup = compileDetectorGroup(resources, dg); if (!compiled_detectorgroup) { return nullptr; } detector_groups.emplace_back(std::move(compiled_detectorgroup)); } for (const auto& action : ruleset.acts) { auto compiled_action = compilePlugin(resources, action); if (!compiled_action) { return nullptr; } actions.emplace_back(std::move(compiled_action)); } return std::make_unique( ruleset.name, std::move(detector_groups), std::move(actions), ruleset.dropin.disable_on_drop_in, ruleset.dropin.detectorgroups_enabled, ruleset.dropin.actiongroup_enabled, silenced_logs); } } // namespace namespace Oomd { namespace Config2 { std::unique_ptr compile(const IR::Root& root) { Engine::MonitoredResources resources; std::vector> rulesets; for (const auto& ruleset : root.rulesets) { auto compiled_ruleset = compileRuleset(resources, ruleset, false); if (!compiled_ruleset) { return nullptr; } rulesets.emplace_back(std::move(compiled_ruleset)); } return std::make_unique( std::move(resources), std::move(rulesets)); } std::optional compileDropIn( const IR::Root& root, const IR::Root& dropin) { DropInUnit ret; for (const auto& dropin_rs : dropin.rulesets) { bool found_target = false; for (const auto& rs : root.rulesets) { if (rs.name == dropin_rs.name) { found_target = true; Engine::MonitoredResources dummy; auto target = compileRuleset(dummy, rs, false); if (!target) { return std::nullopt; } auto compiled_drop = compileRuleset(ret.resources, dropin_rs, true); if (!compiled_drop) { return std::nullopt; } if (!target->mergeWithDropIn(std::move(compiled_drop))) { OLOG << "Could not merge drop in ruleset=" << dropin_rs.name; return std::nullopt; } ret.rulesets.emplace_back(std::move(target)); break; } } if (!found_target) { OLOG << "Could not locate targeted ruleset=" << dropin_rs.name; return std::nullopt; } } return ret; } } // namespace Config2 } // namespace Oomd oomd-0.3.1/src/oomd/config/ConfigCompiler.h000066400000000000000000000031271362162667600205360ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include "oomd/config/ConfigTypes.h" #include "oomd/engine/Engine.h" namespace Oomd { namespace Config2 { struct DropInUnit { Engine::MonitoredResources resources; std::vector> rulesets; }; /* * Compiles IR into Oomd::Engine data structures. Also performs full * validation on the IR. If the IR is invalid, @method compile will * print error messages and return a nullptr. */ std::unique_ptr compile(const IR::Root& root); /* * Compiles a drop in ruleset against a @class IR::Root config. Has * the same semantics as @method compile. The compiled ruleset and * @class Engine::MonitoredResources must be injected into an * existing @class Engine::Engine. */ std::optional compileDropIn( const IR::Root& root, const IR::Root& dropin); } // namespace Config2 } // namespace Oomd oomd-0.3.1/src/oomd/config/ConfigCompilerTest.cpp000066400000000000000000000352331362162667600217340ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "oomd/PluginRegistry.h" #include "oomd/config/ConfigCompiler.h" #include "oomd/config/ConfigTypes.h" #include "oomd/engine/BasePlugin.h" using namespace Oomd; using namespace Oomd::Config2; using namespace Oomd::Engine; using namespace testing; namespace { int count; int stored_count; } // namespace static constexpr auto kRandomCgroupFs = "/some/random/fs"; static constexpr auto kRandomCgroupDependency = "/some/random/cgroup"; namespace Oomd { class ContinuePlugin : public BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const PluginArgs& /* unused */) override { return 0; } PluginRet run(OomdContext& /* unused */) override { return PluginRet::CONTINUE; } static ContinuePlugin* create() { return new ContinuePlugin(); } ~ContinuePlugin() override = default; }; class StopPlugin : public BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const PluginArgs& /* unused */) override { return 0; } PluginRet run(OomdContext& /* unused */) override { return PluginRet::STOP; } static StopPlugin* create() { return new StopPlugin(); } ~StopPlugin() override = default; }; class IncrementCountPlugin : public BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const PluginArgs& /* unused */) override { return 0; } PluginRet run(OomdContext& /* unused */) override { ++count; return PluginRet::CONTINUE; } static IncrementCountPlugin* create() { return new IncrementCountPlugin(); } ~IncrementCountPlugin() override = default; }; class StoreCountPlugin : public BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const PluginArgs& /* unused */) override { return 0; } PluginRet run(OomdContext& /* unused */) override { stored_count = count; return PluginRet::CONTINUE; } static StoreCountPlugin* create() { return new StoreCountPlugin(); } ~StoreCountPlugin() override = default; }; class RegistrationPlugin : public BasePlugin { public: int init( Engine::MonitoredResources& resources, const PluginArgs& /* unused */) override { resources.emplace(kRandomCgroupFs, kRandomCgroupDependency); return 0; } PluginRet run(OomdContext& /* unused */) override { return PluginRet::CONTINUE; } static RegistrationPlugin* create() { return new RegistrationPlugin(); } ~RegistrationPlugin() override = default; }; class NoInitPlugin : public BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const PluginArgs& /* unused */) override { return 1; } PluginRet run(OomdContext& /* unused */) override { return PluginRet::CONTINUE; } static NoInitPlugin* create() { return new NoInitPlugin(); } ~NoInitPlugin() override = default; }; REGISTER_PLUGIN(Continue, ContinuePlugin::create); REGISTER_PLUGIN(Stop, StopPlugin::create); REGISTER_PLUGIN(IncrementCount, IncrementCountPlugin::create); REGISTER_PLUGIN(StoreCount, StoreCountPlugin::create); REGISTER_PLUGIN(Registration, RegistrationPlugin::create); REGISTER_PLUGIN(NoInit, NoInitPlugin::create); } // namespace Oomd class CompilerTest : public ::testing::Test { public: CompilerTest() { count = 0; stored_count = 0; } std::unique_ptr<::Oomd::Engine::Engine> compile() { return Config2::compile(root); } OomdContext context; IR::Root root; }; class DropInCompilerTest : public ::testing::Test { public: DropInCompilerTest() { count = 0; stored_count = 0; } std::unique_ptr<::Oomd::Engine::Engine> compileBase() { return compile(root); } std::optional compileDropIn() { return ::Oomd::Config2::compileDropIn(root, dropin_ir); } OomdContext context; IR::Root root; IR::Root dropin_ir; }; TEST_F(CompilerTest, IncrementCount) { IR::Detector cont; cont.name = "Continue"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dgroup{"group1", {std::move(cont)}}; IR::Ruleset ruleset{"ruleset1", {std::move(dgroup)}, {std::move(increment)}}; root.rulesets.emplace_back(std::move(ruleset)); auto engine = compile(); ASSERT_TRUE(engine); for (int i = 0; i < 3; ++i) { engine->runOnce(context); } EXPECT_EQ(count, 3); } TEST_F(CompilerTest, MultiGroupIncrementCount) { IR::Detector cont; cont.name = "Continue"; IR::Detector stop; stop.name = "Stop"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dgroup1{"group1", {cont}}; IR::DetectorGroup dgroup2{"group2", {stop}}; IR::DetectorGroup dgroup3{"group3", {cont}}; IR::Ruleset ruleset1{"ruleset1", {dgroup1}, {increment}}; IR::Ruleset ruleset2{"ruleset2", {dgroup2}, {increment}}; IR::Ruleset ruleset3{"ruleset3", {dgroup3}, {increment}}; root.rulesets.emplace_back(std::move(ruleset1)); root.rulesets.emplace_back(std::move(ruleset2)); root.rulesets.emplace_back(std::move(ruleset3)); auto engine = compile(); ASSERT_TRUE(engine); for (int i = 0; i < 3; ++i) { engine->runOnce(context); } EXPECT_EQ(count, 6); } TEST_F(CompilerTest, IncrementCountNoop) { IR::Detector stop; stop.name = "Stop"; IR::Detector cont; cont.name = "Continue"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dgroup{"group1", {std::move(cont), std::move(stop)}}; IR::Ruleset ruleset{"ruleset1", {std::move(dgroup)}, {std::move(increment)}}; root.rulesets.emplace_back(std::move(ruleset)); auto engine = compile(); ASSERT_TRUE(engine); for (int i = 0; i < 3; ++i) { engine->runOnce(context); } EXPECT_EQ(count, 0); } TEST_F(CompilerTest, MonitoredResources) { IR::Detector cont; cont.name = "Continue"; IR::Action reg; reg.name = "Registration"; IR::DetectorGroup dgroup{"group1", {std::move(cont)}}; IR::Ruleset ruleset{"ruleset1", {std::move(dgroup)}, {std::move(reg)}}; root.rulesets.emplace_back(std::move(ruleset)); auto engine = compile(); ASSERT_TRUE(engine); EXPECT_THAT( engine->getMonitoredResources(), Contains(CgroupPath(kRandomCgroupFs, kRandomCgroupDependency))); } TEST_F(CompilerTest, NoInitPlugin) { IR::Detector noinit; noinit.name = "NoInit"; IR::Action reg; reg.name = "Register"; IR::DetectorGroup dgroup{"group1", {std::move(noinit)}}; IR::Ruleset ruleset{"ruleset1", {std::move(dgroup)}, {std::move(reg)}}; root.rulesets.emplace_back(std::move(ruleset)); auto engine = compile(); EXPECT_FALSE(engine); } TEST_F(CompilerTest, SilenceLogsParse) { IR::Detector cont; cont.name = "Continue"; IR::Action cont_act; cont_act.name = "Continue"; IR::DetectorGroup dgroup{.name = "group1", .detectors = {cont}}; IR::Ruleset ruleset{ .name = "ruleset1", .dgs = {std::move(dgroup)}, .acts = {cont_act}, .silence_logs = "engine,plugins", }; root.rulesets.emplace_back(std::move(ruleset)); auto engine = compile(); EXPECT_TRUE(engine); root.rulesets[0].silence_logs = " engine, plugins \n"; engine = compile(); EXPECT_TRUE(engine); root.rulesets[0].silence_logs = "engine,asdf"; engine = compile(); EXPECT_FALSE(engine); } TEST_F(DropInCompilerTest, DropInConfig) { IR::Detector cont; cont.name = "Continue"; IR::Action noop; noop.name = "Continue"; IR::DetectorGroup dg{"dg", {cont}}; IR::Ruleset rs{"rs", {dg}, {noop}, IR::DropIn{.actiongroup_enabled = true}}; root.rulesets.emplace_back(std::move(rs)); IR::Ruleset dropin_rs; dropin_rs.name = "rs"; IR::Action increment; increment.name = "IncrementCount"; dropin_rs.acts.emplace_back(std::move(increment)); dropin_ir.rulesets.emplace_back(std::move(dropin_rs)); auto engine = compileBase(); ASSERT_TRUE(engine); engine->runOnce(context); EXPECT_EQ(count, 0); auto dropin = compileDropIn(); ASSERT_TRUE(dropin.has_value()); EXPECT_EQ(dropin->rulesets.size(), 1); EXPECT_TRUE(engine->addDropInConfig(0, std::move(dropin->rulesets.at(0)))); engine->runOnce(context); EXPECT_EQ(count, 1); } TEST_F(DropInCompilerTest, MultipleDropInConfigOrdering) { IR::Detector cont; cont.name = "Continue"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dg{"dg", {cont}}; IR::Ruleset rs{ "rs", {dg}, {increment}, IR::DropIn{.actiongroup_enabled = true}}; root.rulesets.emplace_back(std::move(rs)); // First drop in config IR::Ruleset dropin_rs; dropin_rs.name = "rs"; dropin_rs.acts.emplace_back(increment); dropin_ir.rulesets.emplace_back(std::move(dropin_rs)); // Compile the base config auto engine = compileBase(); ASSERT_TRUE(engine); // Add the first drop in config in auto dropin = compileDropIn(); ASSERT_TRUE(dropin.has_value()); EXPECT_EQ(dropin->rulesets.size(), 1); EXPECT_TRUE(engine->addDropInConfig(0, std::move(dropin->rulesets.at(0)))); // Second drop in config dropin_rs = {}; dropin_rs.name = "rs"; IR::Action store; store.name = "StoreCount"; dropin_rs.acts.emplace_back(std::move(store)); dropin_ir.rulesets[0] = std::move(dropin_rs); // Now add the second drop in config in. // // We expect this to be run before the previous drop in config auto dropin2 = compileDropIn(); ASSERT_TRUE(dropin2.has_value()); EXPECT_EQ(dropin2->rulesets.size(), 1); EXPECT_TRUE(engine->addDropInConfig(1, std::move(dropin2->rulesets.at(0)))); engine->runOnce(context); EXPECT_EQ(count, 2); EXPECT_EQ(stored_count, 0); } TEST_F(DropInCompilerTest, DisablesBase) { IR::Detector cont; cont.name = "Continue"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dg{"dg", {cont}}; IR::Ruleset rs{"rs", {dg}, {increment}, IR::DropIn{ .disable_on_drop_in = true, .actiongroup_enabled = true, }}; root.rulesets.emplace_back(std::move(rs)); IR::Ruleset dropin_rs; dropin_rs.name = "rs"; IR::Action noop; noop.name = "Continue"; dropin_rs.acts.emplace_back(noop); dropin_ir.rulesets.emplace_back(std::move(dropin_rs)); auto engine = compileBase(); ASSERT_TRUE(engine); engine->runOnce(context); EXPECT_EQ(count, 1); auto dropin = compileDropIn(); ASSERT_TRUE(dropin.has_value()); EXPECT_EQ(dropin->rulesets.size(), 1); EXPECT_TRUE(engine->addDropInConfig(0, std::move(dropin->rulesets.at(0)))); engine->runOnce(context); EXPECT_EQ(count, 1); } TEST_F(DropInCompilerTest, PermissionDenied) { IR::Detector cont; cont.name = "Continue"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dg{"dg", {cont}}; IR::Ruleset rs{"rs", {dg}, {increment}, IR::DropIn{ .disable_on_drop_in = false, .detectorgroups_enabled = false, .actiongroup_enabled = false, }}; root.rulesets.emplace_back(std::move(rs)); IR::Ruleset dropin_rs; dropin_rs.name = "rs"; IR::Action noop; noop.name = "Continue"; dropin_rs.acts.emplace_back(noop); dropin_ir.rulesets.emplace_back(std::move(dropin_rs)); auto engine = compileBase(); ASSERT_TRUE(engine); auto dropin = compileDropIn(); EXPECT_FALSE(dropin.has_value()); } TEST_F(DropInCompilerTest, RemoveDropIn) { // Base ruleset increments twice IR::Detector cont; cont.name = "Continue"; IR::Action increment; increment.name = "IncrementCount"; IR::DetectorGroup dg{"dg", {cont}}; IR::Ruleset rs{"rs", {dg}, {increment, increment}, IR::DropIn{.actiongroup_enabled = true}}; root.rulesets.emplace_back(std::move(rs)); // Drop in ruleset increments once IR::Ruleset dropin_rs; dropin_rs.name = "rs"; dropin_rs.acts.emplace_back(increment); dropin_ir.rulesets.emplace_back(std::move(dropin_rs)); auto engine = compileBase(); ASSERT_TRUE(engine); engine->runOnce(context); // Only the base EXPECT_EQ(count, 2); auto dropin = compileDropIn(); ASSERT_TRUE(dropin.has_value()); EXPECT_EQ(dropin->rulesets.size(), 1); EXPECT_TRUE(engine->addDropInConfig(0, std::move(dropin->rulesets.at(0)))); engine->runOnce(context); // Base and drop in EXPECT_EQ(count, 5); engine->removeDropInConfig(0); engine->runOnce(context); // Only the base EXPECT_EQ(count, 7); } TEST_F(DropInCompilerTest, MultipleRulesetDropin) { IR::Detector cont; cont.name = "Continue"; IR::Action noop; noop.name = "Continue"; IR::DetectorGroup dg{"dg", {cont}}; IR::Ruleset rs{"rs", {dg}, {noop}, IR::DropIn{ .disable_on_drop_in = true, .actiongroup_enabled = true, }}; IR::Ruleset rs2{"rs2", {dg}, {noop}, IR::DropIn{ .disable_on_drop_in = true, .actiongroup_enabled = true, }}; root.rulesets.emplace_back(std::move(rs)); root.rulesets.emplace_back(std::move(rs2)); // Drop-in ruleset 1 IR::Ruleset dropin_rs; dropin_rs.name = "rs"; IR::Action increment; increment.name = "IncrementCount"; dropin_rs.acts.emplace_back(increment); // Drop-in ruleset 2 IR::Ruleset dropin_rs2; dropin_rs2.name = "rs2"; dropin_rs2.acts.emplace_back(increment); dropin_ir.rulesets.emplace_back(std::move(dropin_rs)); dropin_ir.rulesets.emplace_back(std::move(dropin_rs2)); auto engine = compileBase(); ASSERT_TRUE(engine); engine->runOnce(context); EXPECT_EQ(count, 0); auto dropin = compileDropIn(); ASSERT_TRUE(dropin.has_value()); EXPECT_EQ(dropin->rulesets.size(), 2); EXPECT_TRUE(engine->addDropInConfig(0, std::move(dropin->rulesets.at(0)))); EXPECT_TRUE(engine->addDropInConfig(0, std::move(dropin->rulesets.at(1)))); engine->runOnce(context); EXPECT_EQ(count, 2); // Now see if both are removed engine->removeDropInConfig(0); engine->runOnce(context); EXPECT_EQ(count, 2); } oomd-0.3.1/src/oomd/config/ConfigTypes.cpp000066400000000000000000000053561362162667600204310ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "oomd/Log.h" #include "oomd/config/ConfigTypes.h" namespace { std::string getIndentSpaces(int depth) { return std::string(depth * 2, ' '); } } // namespace namespace Oomd { namespace Config2 { namespace IR { void dumpIR(const Root& root) { int indent = 0; OLOG << getIndentSpaces(indent) << root.rulesets.size() << " Rulesets="; ++indent; for (const auto& ruleset : root.rulesets) { OLOG << getIndentSpaces(indent) << "Ruleset=" << ruleset.name; ++indent; // Print DropIn config OLOG << getIndentSpaces(indent) << "DropIn="; ++indent; OLOG << getIndentSpaces(indent) << "Detectors=" << ruleset.dropin.detectorgroups_enabled; OLOG << getIndentSpaces(indent) << "Actions=" << ruleset.dropin.actiongroup_enabled; OLOG << getIndentSpaces(indent) << "DisableOnDrop=" << ruleset.dropin.disable_on_drop_in; --indent; OLOG << getIndentSpaces(indent) << "SilenceLogs=" << ruleset.silence_logs; // Print DetectorGroup's for (const auto& dg : ruleset.dgs) { OLOG << getIndentSpaces(indent) << "DetectorGroup=" << dg.name; ++indent; // Print Detectors for (const auto& d : dg.detectors) { OLOG << getIndentSpaces(indent) << "Detector=" << d.name; ++indent; OLOG << getIndentSpaces(indent) << "Args="; ++indent; // Print arguments for (const auto& pair : d.args) { OLOG << getIndentSpaces(indent) << pair.first << "=" << pair.second; } indent -= 2; } --indent; } // Print Action's for (const auto& act : ruleset.acts) { OLOG << getIndentSpaces(indent) << "Action=" << act.name; ++indent; OLOG << getIndentSpaces(indent) << "Args="; ++indent; // Print arguments for (const auto& pair : act.args) { OLOG << getIndentSpaces(indent) << pair.first << "=" << pair.second; } indent -= 2; } --indent; } --indent; } } // namespace IR } // namespace Config2 } // namespace Oomd oomd-0.3.1/src/oomd/config/ConfigTypes.h000066400000000000000000000047131362162667600200720ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include namespace Oomd { namespace Config2 { namespace IR { // The IR (intermediate representation) namespace holds the fundamental // datastructures responsible for oomd runtime behavior. The IR was created to // decouple the config language (eg JSON) from the generic behavior of oomd. To // add another config frontend, for example an iptables-like syntax, one only // needs to create a parsing function that can construct an struct IR::Root. // The IR::Root can then be passed to the ConfigCompiler. // // In the most general sense, oomd is configured to wait for DetectorGroups to // fire. If a DetectorGroup fires, then corresponding Actions are taken. A // DetectorGroup is made up of one or more Detectors. // // Both Detector and Action derive from the base Plugin class. A Plugin can // return CONTINUE or STOP. If all Detectors in a DetectorGroup return // CONTINUE, that is to say it does not terminate the Detector chain with a // STOP, then the corresponding Action chain is run. An Action chain runs until // a plugin in the chain returns STOP. struct Plugin { std::string name; std::unordered_map args; }; struct Detector : Plugin {}; struct Action : Plugin {}; struct DetectorGroup { std::string name; std::vector detectors; }; struct DropIn { bool disable_on_drop_in{false}; bool detectorgroups_enabled{false}; bool actiongroup_enabled{false}; }; struct Ruleset { std::string name; std::vector dgs; std::vector acts; DropIn dropin; std::string silence_logs; }; struct Root { std::vector rulesets; }; void dumpIR(const Root& root); } // namespace IR } // namespace Config2 } // namespace Oomd oomd-0.3.1/src/oomd/config/JsonConfigParser.cpp000066400000000000000000000076361362162667600214160ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/config/JsonConfigParser.h" #include #include #include "oomd/Log.h" #include "oomd/util/Fs.h" namespace { void getJson(Json::Value& root, const std::string& input) { std::string errs; Json::CharReaderBuilder rbuilder; rbuilder["allowComments"] = true; rbuilder["collectComments"] = false; std::istringstream sinput(input); bool ok = Json::parseFromStream(rbuilder, sinput, &root, &errs); if (!ok) { OLOG << "Unable to parse JSON: " << errs; throw std::runtime_error("Unable to parse JSON"); } } template T parsePlugin(const Json::Value& plugin) { if (!plugin.isObject()) { return {}; } const auto& name = plugin["name"]; if (!name.isString()) { return {}; } T ret; ret.name = name.asString(); const auto& json_args = plugin["args"]; if (!json_args.isObject()) { return ret; } for (const auto& key : json_args.getMemberNames()) { const auto& value = json_args[key]; // Value has to be a string, number, or bool if (!value.isString() && !value.isNumeric() && !value.isBool()) { return ret; } ret.args[key] = value.asString(); } return ret; } Oomd::Config2::IR::DetectorGroup parseDetectorGroup( const Json::Value& detector_group) { Oomd::Config2::IR::DetectorGroup ir_detectorgroup; if (!detector_group.isArray()) { return {}; } for (Json::ArrayIndex i = 0; i < detector_group.size(); ++i) { if (i == 0 && detector_group[i].isString()) { ir_detectorgroup.name = detector_group[i].asString(); continue; } // else: all other indicies (ie detectors) const auto& detector = detector_group[i]; ir_detectorgroup.detectors.emplace_back( parsePlugin(detector)); } return ir_detectorgroup; } Oomd::Config2::IR::DropIn parseDropIn(const Json::Value& dropin) { Oomd::Config2::IR::DropIn ir_dropin; ir_dropin.disable_on_drop_in = dropin.get("disable-on-drop-in", false).asBool(); ir_dropin.detectorgroups_enabled = dropin.get("detectors", false).asBool(); ir_dropin.actiongroup_enabled = dropin.get("actions", false).asBool(); return ir_dropin; } Oomd::Config2::IR::Ruleset parseRuleset(const Json::Value& ruleset) { Oomd::Config2::IR::Ruleset ir_ruleset; ir_ruleset.name = ruleset.get("name", "").asString(); ir_ruleset.dropin = parseDropIn(ruleset.get("drop-in", {})); ir_ruleset.silence_logs = ruleset.get("silence-logs", {}).asString(); for (const auto& detector_group : ruleset.get("detectors", {})) { ir_ruleset.dgs.emplace_back(parseDetectorGroup(detector_group)); } for (const auto& action : ruleset.get("actions", {})) { ir_ruleset.acts.emplace_back( parsePlugin(action)); } return ir_ruleset; } } // namespace namespace Oomd { namespace Config2 { std::unique_ptr JsonConfigParser::parse(const std::string& input) { Json::Value json_root; getJson(json_root, input); auto ir_root = std::make_unique(); for (const auto& ruleset : json_root.get("rulesets", {})) { ir_root->rulesets.emplace_back(parseRuleset(ruleset)); } IR::dumpIR(*ir_root); return ir_root; } } // namespace Config2 } // namespace Oomd oomd-0.3.1/src/oomd/config/JsonConfigParser.h000066400000000000000000000017201362162667600210470ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include "oomd/config/ConfigTypes.h" namespace Oomd { namespace Config2 { class JsonConfigParser { public: std::unique_ptr parse(const std::string& input); }; } // namespace Config2 } // namespace Oomd oomd-0.3.1/src/oomd/config/JsonConfigParserTest.cpp000066400000000000000000000072001362162667600222410ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "oomd/config/JsonConfigParser.h" using namespace Oomd::Config2; using namespace testing; constexpr auto kConfig_1_0_0 = "oomd/fixtures/oomd.json"; TEST(JsonConfigParserTest, LoadIR) { std::ifstream file(kConfig_1_0_0, std::ios::in); ASSERT_TRUE(file.is_open()); std::ostringstream buffer; buffer << file.rdbuf(); // Make sure the IR was generated JsonConfigParser parser; auto root = parser.parse(buffer.str()); ASSERT_TRUE(root); // Check root values ASSERT_EQ(root->rulesets.size(), 2); // Check first ruleset const auto& first = root->rulesets[0]; EXPECT_EQ(first.name, "my first ruleset"); ASSERT_EQ(first.dgs.size(), 1); EXPECT_EQ(first.silence_logs, "engine,plugins"); // Check first ruleset drop in config EXPECT_TRUE(first.dropin.detectorgroups_enabled); EXPECT_FALSE(first.dropin.actiongroup_enabled); EXPECT_TRUE(first.dropin.disable_on_drop_in); // Check first ruleset's first DetectorGroup const auto& first_dg = first.dgs[0]; EXPECT_EQ(first_dg.name, "group1"); ASSERT_EQ(first_dg.detectors.size(), 2); EXPECT_EQ(first_dg.detectors[0].name, "pressure_rising_beyond"); EXPECT_EQ(first_dg.detectors[0].args.at("cgroup"), "workload.slice"); EXPECT_EQ(first_dg.detectors[0].args.at("resource"), "memory"); EXPECT_EQ(first_dg.detectors[0].args.at("threshold"), "5"); EXPECT_EQ(first_dg.detectors[1].name, "pressure_rising_beyond"); EXPECT_EQ(first_dg.detectors[1].args.at("cgroup"), "system.slice"); EXPECT_EQ(first_dg.detectors[1].args.at("resource"), "memory"); EXPECT_EQ(first_dg.detectors[1].args.at("threshold"), "40"); // Check first ruleset's actions ASSERT_EQ(first.acts.size(), 1); EXPECT_EQ(first.acts[0].name, "kill_by_memory_size_or_growth"); EXPECT_EQ(first.acts[0].args.at("cgroup"), "system.slice"); // Check second ruleset const auto& second = root->rulesets[1]; EXPECT_EQ(second.name, "low swap ruleset"); ASSERT_EQ(second.dgs.size(), 1); // Check second ruleset's first DetectorGroup const auto& second_dg = second.dgs[0]; EXPECT_EQ(second_dg.name, "my other group"); ASSERT_EQ(second_dg.detectors.size(), 1); EXPECT_EQ(second_dg.detectors[0].name, "swap_free"); EXPECT_EQ(second_dg.detectors[0].args.at("threshold_pct"), "15"); // Check second ruleset's actions ASSERT_EQ(second.acts.size(), 3); EXPECT_EQ(second.acts[0].name, "kill_by_swap_usage"); EXPECT_EQ(second.acts[0].args.at("cgroup"), "system.slice"); EXPECT_EQ(second.acts[1].name, "kill_by_swap_usage"); EXPECT_EQ( second.acts[1].args.at("cgroup"), "workload.slice/workload-wdb.slice"); EXPECT_EQ(second.acts[2].name, "kill_by_swap_usage"); EXPECT_EQ( second.acts[2].args.at("cgroup"), "workload.slice/workload-tw.slice"); } TEST(JsonConfigParserTest, LoadIRBadInput) { JsonConfigParser parser; ASSERT_THROW(parser.parse("not a json string"), std::runtime_error); } oomd-0.3.1/src/oomd/engine/000077500000000000000000000000001362162667600154625ustar00rootroot00000000000000oomd-0.3.1/src/oomd/engine/BasePlugin.h000066400000000000000000000045661362162667600176770ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include "oomd/OomdContext.h" #include "oomd/include/CgroupPath.h" #include "oomd/include/Types.h" namespace Oomd { namespace Engine { enum class PluginRet { CONTINUE = 0, STOP, }; using MonitoredResources = std::unordered_set; using PluginArgs = std::unordered_map; class BasePlugin { public: /* * Plugins can register cgroups they want monitored by adding paths to * @param resources. * * Config arguments are passed via @param args. If some required args are * missing, the plugin can return a non-zero value and oomd will abort * initialization. * * * @return 0 on successful initialization. */ virtual int init(MonitoredResources& resources, const PluginArgs& args) = 0; /* * This is the main work method every plugin will implement. * * If part of a detector chain, a PluginRet::STOP will terminate and fail * the detector chain. If the entire detector chain returns * PluginRet::CONTINUE, the detector chain will succeed. * * If part of an action chain, a PluginRet::STOP will terminate further * execution of the action chain. PluginRet::Continue continues execution. * * Plugins may store state between invocations inside the object instance. * * @return @class PluginRet */ virtual PluginRet run(OomdContext& context) = 0; virtual void setName(const std::string& name) { name_ = name; } virtual const std::string& getName() const { return name_; } virtual ~BasePlugin() = default; private: std::string name_; }; } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/DetectorGroup.cpp000066400000000000000000000035641362162667600207640ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/engine/DetectorGroup.h" #include "oomd/Log.h" #include "oomd/OomdContext.h" #include "oomd/engine/EngineTypes.h" namespace Oomd { namespace Engine { DetectorGroup::DetectorGroup( const std::string& name, std::vector> detectors) : name_(name), detectors_(std::move(detectors)) {} bool DetectorGroup::check(OomdContext& context, uint32_t silenced_logs) { // Note we're running all Detectors so that any detectors keeping sliding // windows can update their window bool triggered = true; for (const auto& detector : detectors_) { if (silenced_logs & LogSources::PLUGINS) { OLOG << LogStream::Control::DISABLE; } PluginRet ret = detector->run(context); if (silenced_logs & LogSources::PLUGINS) { OLOG << LogStream::Control::ENABLE; } switch (ret) { case PluginRet::CONTINUE: continue; case PluginRet::STOP: triggered = false; break; // missing default to protect against future PluginRet vals } } return triggered; } const std::string& DetectorGroup::name() const { return name_; } } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/DetectorGroup.h000066400000000000000000000025341362162667600204250ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include "oomd/OomdContext.h" #include "oomd/engine/BasePlugin.h" namespace Oomd { namespace Engine { class DetectorGroup { public: DetectorGroup( const std::string& name, std::vector> detectors); ~DetectorGroup() = default; /* * @return true if no @class Detector returns PluginRet::STOP. */ bool check(OomdContext& context, uint32_t silenced_logs); const std::string& name() const; private: std::string name_; std::vector> detectors_; }; } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/Engine.cpp000066400000000000000000000065101362162667600173750ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/engine/Engine.h" #include #include "oomd/Log.h" #include "oomd/Stats.h" #include "oomd/include/Assert.h" #include "oomd/include/CoreStats.h" namespace Oomd { namespace Engine { Engine::Engine( MonitoredResources resources, std::vector> rulesets) : resources_(std::move(resources)) { for (auto& rs : rulesets) { if (rs) { rulesets_.emplace_back(BaseRuleset{.ruleset = std::move(rs)}); } } } bool Engine::addDropInConfig(size_t tag, std::unique_ptr ruleset) { if (!ruleset) { return false; } // First located the targeted ruleset auto it = std::find_if( rulesets_.begin(), rulesets_.end(), [&](const BaseRuleset& b) { return b.ruleset->getName() == ruleset->getName(); }); if (it == rulesets_.end()) { OLOG << "Error: could not locate targeted ruleset: " << ruleset->getName(); return false; } // Add drop in ruleset DropInRuleset dir; dir.tag = tag; dir.ruleset = std::move(ruleset); // NB: the drop in rulesets must be added/executed LIFO order. it->dropins.emplace_front(std::move(dir)); // Mark base ruleset at targeted it->ruleset->markDropInTargeted(); Oomd::incrementStat(CoreStats::kNumDropInAdds, 1); return true; } void Engine::removeDropInConfig(size_t tag) { auto pred = [&](const DropInRuleset& dir) { return dir.tag == tag; }; for (auto& base : rulesets_) { auto new_end = std::remove_if(base.dropins.begin(), base.dropins.end(), pred); int n = base.dropins.cend() - new_end; if (!n) { continue; } // Delete properly tagged drop in rulesets as requested base.dropins.erase(new_end, base.dropins.end()); // Mark base ruleset as untargeted for (int i = 0; i < n; ++i) { base.ruleset->markDropInUntargeted(); } // Make sure to decrement counter if there's a remove. This is to // normalize the count in case the same drop-in config is added/ // removed a bunch for some reason. Oomd::incrementStat(CoreStats::kNumDropInAdds, -n); } } void Engine::runOnce(OomdContext& context) { uint32_t nr_dropins_run = 0; for (const auto& base : rulesets_) { // Run all the drop in rulesets first for (const auto& dropin : base.dropins) { if (dropin.ruleset) { nr_dropins_run += dropin.ruleset->runOnce(context); } } // Now run the base ruleset base.ruleset->runOnce(context); } Oomd::incrementStat(CoreStats::kNumDropInFired, nr_dropins_run); } const MonitoredResources& Engine::getMonitoredResources() const { return resources_; } } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/Engine.h000066400000000000000000000043731362162667600170470ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include "oomd/OomdContext.h" #include "oomd/engine/BasePlugin.h" #include "oomd/engine/Ruleset.h" namespace Oomd { namespace Engine { class Engine { public: Engine( MonitoredResources resources, std::vector> rulesets); ~Engine() = default; /* * Adds a drop in config to the running engine. * * @param tag is a unique tag to be associated with the drop * in config. Removing the dropped in config requires the original tag. * * @returns false if @param ruleset's target is not found. true otherwise. */ bool addDropInConfig(size_t tag, std::unique_ptr ruleset); /* * Removes drop in configs associated with @param tag */ void removeDropInConfig(size_t tag); /* * Runs every @class Ruleset once. */ void runOnce(OomdContext& context); /* * This resources instance is passed to all plugins that will run * inside this engine. Plugins can then declare what resources they * need with the engine. Then the oomd runtime can extract what resources * it will need to poll. */ const MonitoredResources& getMonitoredResources() const; private: struct DropInRuleset { size_t tag{0}; // required field std::unique_ptr ruleset; }; struct BaseRuleset { std::unique_ptr ruleset; std::deque dropins; }; MonitoredResources resources_; std::vector rulesets_; }; } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/EngineTypes.h000066400000000000000000000015571362162667600200750ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once namespace Oomd { namespace Engine { enum LogSources { ENGINE = 1 << 0, PLUGINS = 1 << 1, }; } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/Ruleset.cpp000066400000000000000000000101211362162667600176040ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/engine/Ruleset.h" #include "oomd/Log.h" #include "oomd/engine/EngineTypes.h" namespace Oomd { namespace Engine { Ruleset::Ruleset( const std::string& name, std::vector> detector_groups, std::vector> action_group, bool disable_on_drop_in, bool detectorgroups_dropin_enabled, bool actiongroup_dropin_enabled, uint32_t silence_logs) : name_(name), detector_groups_(std::move(detector_groups)), action_group_(std::move(action_group)), disable_on_drop_in_(disable_on_drop_in), detectorgroups_dropin_enabled_(detectorgroups_dropin_enabled), actiongroup_dropin_enabled_(actiongroup_dropin_enabled), silenced_logs_(silence_logs) {} bool Ruleset::mergeWithDropIn(std::unique_ptr ruleset) { if (!ruleset) { OLOG << "Error: merging with null ruleset"; return false; } if (ruleset->detector_groups_.size()) { if (!detectorgroups_dropin_enabled_) { OLOG << "Error: DetectorGroup drop-in configs disabled"; return false; } detector_groups_ = std::move(ruleset->detector_groups_); } if (ruleset->action_group_.size()) { if (!actiongroup_dropin_enabled_) { OLOG << "Error: Action drop-in configs disabled"; return false; } action_group_ = std::move(ruleset->action_group_); } return true; } void Ruleset::markDropInTargeted() { ++numTargeted_; if (disable_on_drop_in_ && numTargeted_) { enabled_ = false; } } void Ruleset::markDropInUntargeted() { --numTargeted_; if (numTargeted_ <= 0) { enabled_ = true; } } uint32_t Ruleset::runOnce(OomdContext& context) { if (!enabled_) { return 0; } // If any DetectorGroup fires, then begin running action chain // // Note we're still check()'ing the detector groups so that any detectors // keeping sliding windows can update their window bool run_actions = false; for (const auto& dg : detector_groups_) { if (dg->check(context, silenced_logs_) && !run_actions) { if (!(silenced_logs_ & LogSources::ENGINE)) { OLOG << "DetectorGroup=" << dg->name() << " has fired for Ruleset=" << name_ << ". Running action chain."; } run_actions = true; context.setActionContext({name_, dg->name()}); } } if (!run_actions) { return 0; } // Begin running action chain for (const auto& action : action_group_) { if (!(silenced_logs_ & LogSources::ENGINE)) { OLOG << "Running Action=" << action->getName(); } if (silenced_logs_ & LogSources::PLUGINS) { OLOG << LogStream::Control::DISABLE; } PluginRet ret = action->run(context); if (silenced_logs_ & LogSources::PLUGINS) { OLOG << LogStream::Control::ENABLE; } switch (ret) { case PluginRet::CONTINUE: if (!(silenced_logs_ & LogSources::ENGINE)) { OLOG << "Action=" << action->getName() << " returned CONTINUE. Continuing action chain."; } continue; case PluginRet::STOP: if (!(silenced_logs_ & LogSources::ENGINE)) { OLOG << "Action=" << action->getName() << " returned STOP. Terminating action chain."; } break; // break out of switch // missing default to protect against future PluginRet vals } break; } return 1; } } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/engine/Ruleset.h000066400000000000000000000045771362162667600172730ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/OomdContext.h" #include "oomd/engine/BasePlugin.h" #include "oomd/engine/DetectorGroup.h" namespace Oomd { namespace Engine { class Ruleset { public: Ruleset( const std::string& name, std::vector> detector_groups, std::vector> action_group, bool disable_on_drop_in = false, bool detectorgroups_dropin_enabled = false, bool actiongroup_dropin_enabled = false, uint32_t silenced_logs = 0); ~Ruleset() = default; /* * Merges this ruleset with @param ruleset with priority given to @param * ruleset. * * @returns false if @param ruleset tries to override configs to ruleset * members which do not have drop in turned on. true otherwise. */ [[nodiscard]] bool mergeWithDropIn(std::unique_ptr ruleset); /* * Mark/unmark this ruleset as being targeted by an active drop in. */ void markDropInTargeted(); void markDropInUntargeted(); /* * Runs the all the DetectorGroup's. If any of them fires, then begin * running the action chain. * * @returns 1 if we attempted to run the action chain. 0 otherwise. */ uint32_t runOnce(OomdContext& context); const std::string& getName() const { return name_; } private: std::string name_; std::vector> detector_groups_; std::vector> action_group_; bool enabled_{true}; bool disable_on_drop_in_{false}; bool detectorgroups_dropin_enabled_{false}; bool actiongroup_dropin_enabled_{false}; uint32_t silenced_logs_{0}; int32_t numTargeted_{0}; }; } // namespace Engine } // namespace Oomd oomd-0.3.1/src/oomd/etc/000077500000000000000000000000001362162667600147705ustar00rootroot00000000000000oomd-0.3.1/src/oomd/etc/desktop.json000066400000000000000000000061771362162667600173470ustar00rootroot00000000000000// // Basic configuration for a desktop linux machine // { "rulesets": [ { "name": "system overview", "silence-logs": "engine", "detectors": [ [ "records system stats", { "name": "dump_cgroup_overview", "args": { "cgroup": "system.slice" } } ] ], "actions": [ { "name": "continue", "args": { } } ] }, { "name": "user session protection", "detectors": [ [ "user pressure above 60 for 30s", { "name": "pressure_above", "args": { "cgroup": "user.slice", "resource": "memory", "threshold": "60", "duration": "30" } }, { "name": "memory_reclaim", "args": { "cgroup": "user.slice", "duration": "10" } } ], [ "system pressure above 80 for 60s", { "name": "pressure_above", "args": { "cgroup": "system.slice", "resource": "memory", "threshold": "80", "duration": "60" } }, { "name": "memory_reclaim", "args": { "cgroup": "system.slice", "duration": "10" } } ] ], "actions": [ { "name": "kill_by_memory_size_or_growth", "args": { "cgroup": "user.slice/user-*.slice/session-*.scope,user.slice/user-*.slice/user@*.service/*,system.slice/*" } } ] }, { "name": "protection against low swap", "detectors": [ [ "free swap goes below 10 percent", { "name": "swap_free", "args": { "threshold_pct": "10" } } ] ], "actions": [ { "name": "kill_by_swap_usage", "args": { "cgroup": "user.slice/user-*.slice/session-*.scope,user.slice/user-*.slice/user@*.service/*,system.slice/*" } } ] } ] } oomd-0.3.1/src/oomd/etc/oomd.service.in000066400000000000000000000003651362162667600177210ustar00rootroot00000000000000[Unit] Description=Userland out-of-memory killer daemon After=system.slice [Service] ExecStart=@bindir@/oomd --interval 1 --config @oomdconfdir@/oomd.json Restart=always SyslogIdentifier=oomd MemoryLow=64M [Install] WantedBy=multi-user.target oomd-0.3.1/src/oomd/fixtures/000077500000000000000000000000001362162667600160665ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/FsFixture.cpp000066400000000000000000000245061362162667600205200ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/fixtures/FsFixture.h" namespace Oomd { constexpr auto kDataMeminfo = R"(MemTotal: 58616708 kB MemFree: 2955848 kB MemAvailable: 49878948 kB Buffers: 4970160 kB Cached: 35172508 kB SwapCached: 0 kB Active: 27822776 kB Inactive: 19032824 kB Active(anon): 4024064 kB Inactive(anon): 2822876 kB Active(file): 23798712 kB Inactive(file): 16209948 kB Unevictable: 11684 kB Mlocked: 11668 kB SwapTotal: 2097148 kB SwapFree: 1097041 kB Dirty: 24576 kB Writeback: 0 kB AnonPages: 6725380 kB Mapped: 657992 kB Shmem: 130124 kB Slab: 8238080 kB SReclaimable: 7334616 kB SUnreclaim: 903464 kB KernelStack: 45984 kB PageTables: 75756 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 31405500 kB Committed_AS: 21265672 kB VmallocTotal: 34359738367 kB VmallocUsed: 0 kB VmallocChunk: 0 kB HardwareCorrupted: 0 kB AnonHugePages: 71680 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB CmaTotal: 0 kB CmaFree: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 4544368 kB DirectMap2M: 53127168 kB DirectMap1G: 4194304 kB )"; constexpr auto kDataMounts = R"(sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 devtmpfs /dev devtmpfs rw,seclabel,nosuid,size=58710812k,nr_inodes=14677703,mode=755 0 0 securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev 0 0 devpts /dev/pts devpts rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0 tmpfs /run tmpfs rw,seclabel,nosuid,nodev,mode=755 0 0 cgroup2 /sys/fs/cgroup cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0 pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0 bpf /sys/fs/bpf bpf rw,relatime,mode=700 0 0 configfs /sys/kernel/config configfs rw,relatime 0 0 /dev/vda3 / btrfs rw,seclabel,relatime,compress-force=zstd,discard,space_cache,subvolid=5,subvol=/ 0 0 selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0 systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=26,pgrp=1,timeout=0,minproto=5,maxproto=5,direct 0 0 hugetlbfs /dev/hugepages hugetlbfs rw,seclabel,relatime,pagesize=2M 0 0 debugfs /sys/kernel/debug debugfs rw,seclabel,relatime,mode=755 0 0 mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0 sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 nfsd /proc/fs/nfsd nfsd rw,relatime 0 0 fusectl /sys/fs/fuse/connections fusectl rw,relatime 0 0 /dev/vda1 /boot ext4 rw,seclabel,relatime,data=ordered 0 0 none-tw-channel /run/facebook/tw_logging_channel tmpfs rw,seclabel,relatime,size=10240k,nr_inodes=4096 0 0 tracefs /sys/kernel/debug/tracing tracefs rw,seclabel,relatime 0 0 /etc/auto.home.override /home autofs rw,relatime,fd=5,pgrp=2182594,timeout=600,minproto=5,maxproto=5,indirect 0 0 binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc rw,relatime 0 0 tmpfs /run/user/30015 tmpfs rw,seclabel,nosuid,nodev,relatime,size=11744436k,mode=700,uid=30015,gid=30015 0 0 tmpfs /run/user/0 tmpfs rw,seclabel,nosuid,nodev,relatime,size=11744436k,mode=700 0 0 tmpfs /run/user/180401 tmpfs rw,seclabel,nosuid,nodev,relatime,size=11744436k,mode=700,uid=180401,gid=100 0 0 )"; constexpr auto kDataCgMemStat = R"(anon 1294168064 file 3870687232 kernel_stack 7225344 slab 296456192 sock 1228800 shmem 135168 file_mapped 199778304 file_dirty 405504 file_writeback 811008 inactive_anon 296529920 active_anon 997982208 inactive_file 1284907008 active_file 2586988544 unevictable 28672 slab_reclaimable 262471680 slab_unreclaimable 33984512 pgfault 734452455 pgmajfault 4627590 pgrefill 243154 pgscan 787288 pgsteal 770913 pgactivate 13992 pgdeactivate 233504 pglazyfree 0 pglazyfreed 0 workingset_refault 510510 workingset_activate 25608 workingset_restore 3927 workingset_nodereclaim 0 )"; constexpr auto kDataVmstat = R"(first_key 12345 second_key 678910 thirdkey 999999 )"; namespace { using F = Fixture; const auto kEntCgroup = F::makeDir( "cgroup", {F::makeDir( "system.slice", { F::makeDir( "service1.service", {F::makeFile( "cgroup.procs", "456\n" "789\n")}), F::makeDir( "service2.service", {F::makeFile( "memory.pressure", "aggr 128544748770\n" "some 1.11 2.22 3.33\n" "full 4.44 5.55 6.66\n")}), F::makeDir( "service3.service", {F::makeFile( "memory.pressure", "aggr 128544748770\n" "some 1.11 2.22 3.33\n" "full 4.44 5.55 6.66\n" "0 0 0\n" "0 0 0\n" "0 0 0\n" "0 0 0\n")}), F::makeDir( "slice1.slice", {F::makeDir( "service1.service", {F::makeFile( "cgroup.procs", "456\n" "789\n")}), F::makeDir( "service2.service", {F::makeFile( "cgroup.procs", "12\n" "34\n")})}), F::makeFile("cgroup.controllers", "cpu io memory pids\n"), F::makeFile("cgroup.procs", "123\n"), F::makeFile( "cgroup.stat", "nr_descendants 34\n" "nr_dying_descendants 27\n"), F::makeFile( "io.pressure", "some avg10=1.12 avg60=2.23 avg300=3.34 total=134829384401\n" "full avg10=4.45 avg60=5.56 avg300=6.67 total=128544748771\n"), F::makeFile( "io.stat", "1:10 rbytes=1111111 wbytes=2222222 rios=33 wios=44 dbytes=5555555555 dios=6\n" "1:11 rbytes=2222222 wbytes=3333333 rios=44 wios=55 dbytes=6666666666 dios=7\n"), F::makeFile("memory.current", "987654321\n"), F::makeFile("memory.max", "654\n"), F::makeFile("memory.high", "1000\n"), F::makeFile("memory.high.tmp", "2000 20000\n"), F::makeFile("memory.low", "333333\n"), F::makeFile("memory.min", "666\n"), F::makeFile( "memory.pressure", "some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400\n" "full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770\n"), F::makeFile("memory.stat", kDataCgMemStat), F::makeFile("memory.swap.current", "321321\n"), })}); const auto kEntFsData = F::makeDir( "fs_data", { F::makeDir( "dir1", { F::makeFile( "stuff", "hello world\n" "my good man\n" "\n" "1\n"), }), F::makeDir("dir2", {F::makeFile("empty")}), F::makeDir("dir3", {F::makeFile("empty")}), F::makeDir( "wildcard/this/path", { F::makeDir("is/going/to/be/long", {F::makeFile("file")}), F::makeDir("isNOT/going/to/be/long", {F::makeFile("file")}), F::makeDir("WAH/going/to/be/long", {F::makeFile("file")}), }), F::makeFile("file1"), F::makeFile("file2"), F::makeFile("file3"), F::makeFile("file4"), }); const auto kEntProc = F::makeDir( "proc", { F::makeFile("meminfo", kDataMeminfo), F::makeFile("mounts", kDataMounts), F::makeFile("vmstat", kDataVmstat), }); const auto kEntSysDevBlock = F::makeDir( "sys_dev_block", { F::makeDir( "1:0", {F::makeDir("queue", {F::makeFile("rotational", "0\n")})}), F::makeDir( "1:1", {F::makeDir("queue", {F::makeFile("rotational", "1\n")})}), F::makeDir( "1:2", {F::makeDir("queue", {F::makeFile("rotational", "\n")})}), }); const auto kEntFsFixture = F::makeDir( "fs_test", { kEntCgroup, kEntFsData, kEntProc, kEntSysDevBlock, }); } // namespace constexpr auto kCgroupDataDir = "fs_test/cgroup/system.slice"; constexpr auto kFsDataDir = "fs_test/fs_data"; constexpr auto kFsVmstatFile = "fs_test/proc/vmstat"; constexpr auto kFsMeminfoFile = "fs_test/proc/meminfo"; constexpr auto kFsMountsFile = "fs_test/proc/mounts"; constexpr auto kFsDeviceDir = "fs_test/sys_dev_block"; void FsFixture::materialize() { tempFixtureDir_ = F::mkdtempChecked(); kEntFsFixture.second.materialize(tempFixtureDir_, kEntFsFixture.first); } void FsFixture::teardown() { F::rmrChecked(tempFixtureDir_); tempFixtureDir_ = ""; } std::string FsFixture::cgroupDataDir() { return tempFixtureDir_ + "/" + kCgroupDataDir; } std::string FsFixture::fsDataDir() { return tempFixtureDir_ + "/" + kFsDataDir; } std::string FsFixture::fsVmstatFile() { return tempFixtureDir_ + "/" + kFsVmstatFile; } std::string FsFixture::fsMeminfoFile() { return tempFixtureDir_ + "/" + kFsMeminfoFile; } std::string FsFixture::fsMountsFile() { return tempFixtureDir_ + "/" + kFsMountsFile; } std::string FsFixture::fsDeviceDir() { return tempFixtureDir_ + "/" + kFsDeviceDir; } } // namespace Oomd oomd-0.3.1/src/oomd/fixtures/FsFixture.h000066400000000000000000000021171362162667600201570ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/util/Fixture.h" namespace Oomd { class FsFixture { public: void materialize(); void teardown(); std::string cgroupDataDir(); std::string fsDataDir(); std::string fsVmstatFile(); std::string fsMeminfoFile(); std::string fsMountsFile(); std::string fsDeviceDir(); private: std::string tempFixtureDir_{}; }; } // namespace Oomd oomd-0.3.1/src/oomd/fixtures/cgroup/000077500000000000000000000000001362162667600173655ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/missing_control_files.slice/000077500000000000000000000000001362162667600250565ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/missing_control_files.slice/cgroup.controllers000066400000000000000000000000071362162667600306420ustar00rootroot00000000000000memory oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/000077500000000000000000000000001362162667600250745ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/000077500000000000000000000000001362162667600252545ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/000077500000000000000000000000001362162667600255155ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/cgroup.controllers000066400000000000000000000000231362162667600312770ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/memory.current000066400000000000000000000000131362162667600304230ustar00rootroot000000000000002147483648 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/memory.high000066400000000000000000000000041362162667600276600ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/memory.low000066400000000000000000000000131362162667600275420ustar00rootroot000000000000001073741824 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/memory.max000066400000000000000000000000041362162667600275260ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/memory.min000066400000000000000000000000021362162667600275220ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A1/memory.pressure000066400000000000000000000001641362162667600306200ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/000077500000000000000000000000001362162667600255165ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/cgroup.controllers000066400000000000000000000000231362162667600313000ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/memory.current000066400000000000000000000000131362162667600304240ustar00rootroot000000000000002147483648 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/memory.high000066400000000000000000000000041362162667600276610ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/memory.low000066400000000000000000000000131362162667600275430ustar00rootroot000000000000004294967296 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/memory.max000066400000000000000000000000041362162667600275270ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/memory.min000066400000000000000000000000021362162667600275230ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/A2/memory.pressure000066400000000000000000000001641362162667600306210ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/cgroup.controllers000066400000000000000000000000231362162667600310360ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/memory.current000066400000000000000000000000131362162667600301620ustar00rootroot000000000000004294967296 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/memory.high000066400000000000000000000000041362162667600274170ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/memory.low000066400000000000000000000000131362162667600273010ustar00rootroot000000000000002147483648 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/memory.max000066400000000000000000000000041362162667600272650ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/memory.min000066400000000000000000000000021362162667600272610ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/A/memory.pressure000066400000000000000000000001641362162667600303570ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/000077500000000000000000000000001362162667600252555ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/000077500000000000000000000000001362162667600255175ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/cgroup.controllers000066400000000000000000000000231362162667600313010ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/memory.current000066400000000000000000000000131362162667600304250ustar00rootroot000000000000002147483648 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/memory.high000066400000000000000000000000041362162667600276620ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/memory.low000066400000000000000000000000131362162667600275440ustar00rootroot000000000000001073741824 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/memory.max000066400000000000000000000000041362162667600275300ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/memory.min000066400000000000000000000000021362162667600275240ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B1/memory.pressure000066400000000000000000000001641362162667600306220ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/000077500000000000000000000000001362162667600255205ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/cgroup.controllers000066400000000000000000000000231362162667600313020ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/memory.current000066400000000000000000000000131362162667600304260ustar00rootroot000000000000002147483648 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/memory.high000066400000000000000000000000041362162667600276630ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/memory.low000066400000000000000000000000131362162667600275450ustar00rootroot000000000000004294967296 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/memory.max000066400000000000000000000000041362162667600275310ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/memory.min000066400000000000000000000000021362162667600275250ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/B2/memory.pressure000066400000000000000000000001641362162667600306230ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/cgroup.controllers000066400000000000000000000000231362162667600310370ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/memory.current000066400000000000000000000000131362162667600301630ustar00rootroot000000000000004294967296 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/memory.high000066400000000000000000000000041362162667600274200ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/memory.low000066400000000000000000000000131362162667600273020ustar00rootroot000000000000001073741824 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/memory.max000066400000000000000000000000041362162667600272660ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/memory.min000066400000000000000000000000021362162667600272620ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/B/memory.pressure000066400000000000000000000001641362162667600303600ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/000077500000000000000000000000001362162667600252565ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/000077500000000000000000000000001362162667600255215ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/cgroup.controllers000066400000000000000000000000231362162667600313030ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/memory.current000066400000000000000000000000131362162667600304270ustar00rootroot000000000000006442450944 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/memory.high000066400000000000000000000000041362162667600276640ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/memory.low000066400000000000000000000000131362162667600275460ustar00rootroot000000000000005368709120 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/memory.max000066400000000000000000000000041362162667600275320ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/memory.min000066400000000000000000000000021362162667600275260ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/C1/memory.pressure000066400000000000000000000001641362162667600306240ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/cgroup.controllers000066400000000000000000000000231362162667600310400ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/memory.current000066400000000000000000000000131362162667600301640ustar00rootroot000000000000006442450944 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/memory.high000066400000000000000000000000041362162667600274210ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/memory.low000066400000000000000000000000131362162667600273030ustar00rootroot000000000000005368709120 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/memory.max000066400000000000000000000000041362162667600272670ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/memory.min000066400000000000000000000000021362162667600272630ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/C/memory.pressure000066400000000000000000000001641362162667600303610ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/memory.high000066400000000000000000000000041362162667600272370ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/protection_overage.fakeroot/memory.max000066400000000000000000000000041362162667600271050ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/000077500000000000000000000000001362162667600220075ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/cgroup.controllers000066400000000000000000000000231362162667600255710ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/cgroup.procs000066400000000000000000000000041362162667600243500ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/cgroup.subtree_control000066400000000000000000000000231362162667600264340ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/io.pressure000066400000000000000000000001641362162667600242110ustar00rootroot00000000000000some avg10=1.12 avg60=2.23 avg300=3.34 total=134829384401 full avg10=4.45 avg60=5.56 avg300=6.67 total=128544748771 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/io.stat000066400000000000000000000002301362162667600233060ustar00rootroot000000000000001:10 rbytes=1111111 wbytes=2222222 rios=33 wios=44 dbytes=5555555555 dios=6 1:11 rbytes=2222222 wbytes=3333333 rios=44 wios=55 dbytes=6666666666 dios=7 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.current000066400000000000000000000000121362162667600247140ustar00rootroot00000000000000987654321 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.high000066400000000000000000000000051362162667600241530ustar00rootroot000000000000001000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.high.tmp000066400000000000000000000000131362162667600247510ustar00rootroot000000000000002000 20000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.low000066400000000000000000000000071362162667600240370ustar00rootroot00000000000000333333 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.max000066400000000000000000000000041362162667600240200ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.min000066400000000000000000000000041362162667600240160ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.pressure000066400000000000000000000001641362162667600251120ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.stat000066400000000000000000000010721362162667600242140ustar00rootroot00000000000000anon 1294168064 file 3870687232 kernel_stack 7225344 slab 296456192 sock 1228800 shmem 135168 file_mapped 199778304 file_dirty 405504 file_writeback 811008 inactive_anon 296529920 active_anon 997982208 inactive_file 1284907008 active_file 2586988544 unevictable 28672 slab_reclaimable 262471680 slab_unreclaimable 33984512 pgfault 734452455 pgmajfault 4627590 pgrefill 243154 pgscan 787288 pgsteal 770913 pgactivate 13992 pgdeactivate 233504 pglazyfree 0 pglazyfreed 0 workingset_refault 510510 workingset_activate 25608 workingset_restore 3927 workingset_nodereclaim 0 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/memory.swap.current000066400000000000000000000000071362162667600256710ustar00rootroot00000000000000321321 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/000077500000000000000000000000001362162667600251675ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/cgroup.controllers000066400000000000000000000000231362162667600307510ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/cgroup.procs000066400000000000000000000000101362162667600275250ustar00rootroot00000000000000456 789 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.current000066400000000000000000000000071362162667600301000ustar00rootroot00000000000000111111 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.high000066400000000000000000000000051362162667600273330ustar00rootroot000000000000001000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.low000066400000000000000000000000041362162667600272140ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.max000066400000000000000000000000041362162667600272000ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.min000066400000000000000000000000041362162667600271760ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.pressure000066400000000000000000000001641362162667600302720ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service1.service/memory.swap.current000066400000000000000000000000021362162667600310440ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/000077500000000000000000000000001362162667600251705ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/cgroup.controllers000066400000000000000000000000231362162667600307520ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.current000066400000000000000000000000121362162667600300750ustar00rootroot00000000000000987654321 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.high000066400000000000000000000000051362162667600273340ustar00rootroot000000000000001000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.low000066400000000000000000000000041362162667600272150ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.max000066400000000000000000000000041362162667600272010ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.min000066400000000000000000000000041362162667600271770ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.pressure000066400000000000000000000000721362162667600302710ustar00rootroot00000000000000aggr 128544748770 some 1.11 2.22 3.33 full 4.44 5.55 6.66 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service2.service/memory.swap.current000066400000000000000000000000021362162667600310450ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/000077500000000000000000000000001362162667600251715ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/cgroup.controllers000066400000000000000000000000231362162667600307530ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.current000066400000000000000000000000121362162667600300760ustar00rootroot00000000000000987654321 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.high000066400000000000000000000000051362162667600273350ustar00rootroot000000000000001000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.low000066400000000000000000000000041362162667600272160ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.max000066400000000000000000000000041362162667600272020ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.min000066400000000000000000000000041362162667600272000ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.pressure000066400000000000000000000001221362162667600302660ustar00rootroot00000000000000aggr 128544748770 some 1.11 2.22 3.33 full 4.44 5.55 6.66 0 0 0 0 0 0 0 0 0 0 0 0 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service3.service/memory.swap.current000066400000000000000000000000021362162667600310460ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/000077500000000000000000000000001362162667600251725ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/cgroup.controllers000066400000000000000000000000231362162667600307540ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.current000066400000000000000000000000121362162667600300770ustar00rootroot00000000000000987654321 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.high000066400000000000000000000000051362162667600273360ustar00rootroot000000000000001000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.low000066400000000000000000000000041362162667600272170ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.max000066400000000000000000000000041362162667600272030ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.min000066400000000000000000000000041362162667600272010ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.pressure000066400000000000000000000000721362162667600302730ustar00rootroot00000000000000aggr 128544748770 some 1.11 2.22 3.33 full 4.44 5.55 6.66 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/service4.service/memory.swap.current000066400000000000000000000000021362162667600310470ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/000077500000000000000000000000001362162667600242655ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/cgroup.controllers000066400000000000000000000000231362162667600300470ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/cgroup.procs000066400000000000000000000000001362162667600266220ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.current000066400000000000000000000000121362162667600271720ustar00rootroot00000000000000987654321 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.high000066400000000000000000000000051362162667600264310ustar00rootroot000000000000001000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.low000066400000000000000000000000041362162667600263120ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.max000066400000000000000000000000041362162667600262760ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.min000066400000000000000000000000041362162667600262740ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.pressure000066400000000000000000000000721362162667600273660ustar00rootroot00000000000000aggr 128544748770 some 1.11 2.22 3.33 full 4.44 5.55 6.66 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/memory.swap.current000066400000000000000000000000021362162667600301420ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service1.service/000077500000000000000000000000001362162667600274455ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service1.service/cgroup.procs000066400000000000000000000000101362162667600320030ustar00rootroot00000000000000456 789 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service1.service/memory.current000066400000000000000000000000071362162667600323560ustar00rootroot00000000000000111111 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service1.service/memory.low000066400000000000000000000000041362162667600314720ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service1.service/memory.pressure000066400000000000000000000001641362162667600325500ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service1.service/memory.swap.current000066400000000000000000000000021362162667600333220ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service2.service/000077500000000000000000000000001362162667600274465ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service2.service/cgroup.procs000066400000000000000000000000061362162667600320110ustar00rootroot0000000000000012 34 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service2.service/memory.current000066400000000000000000000000071362162667600323570ustar00rootroot00000000000000111111 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service2.service/memory.low000066400000000000000000000000041362162667600314730ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service2.service/memory.pressure000066400000000000000000000001641362162667600325510ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/system.slice/slice1.slice/service2.service/memory.swap.current000066400000000000000000000000021362162667600333230ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/000077500000000000000000000000001362162667600223055ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/cgroup.controllers000066400000000000000000000000231362162667600260670ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/cgroup.procs000066400000000000000000000000041362162667600246460ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/cgroup.subtree_control000066400000000000000000000000231362162667600267320ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/io.pressure000066400000000000000000000001641362162667600245070ustar00rootroot00000000000000some avg10=1.12 avg60=2.23 avg300=3.34 total=134829384401 full avg10=4.45 avg60=5.56 avg300=6.67 total=128544748771 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.current000066400000000000000000000000121362162667600252120ustar00rootroot00000000000000987654321 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.high000066400000000000000000000000041362162667600244500ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.low000066400000000000000000000000071362162667600243350ustar00rootroot00000000000000333333 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.max000066400000000000000000000000041362162667600243160ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.min000066400000000000000000000000041362162667600243140ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.pressure000066400000000000000000000001641362162667600254100ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/memory.swap.current000066400000000000000000000000071362162667600261670ustar00rootroot00000000000000321321 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/000077500000000000000000000000001362162667600254655ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/cgroup.controllers000066400000000000000000000000231362162667600312470ustar00rootroot00000000000000cpu io memory pids oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/cgroup.procs000066400000000000000000000000101362162667600300230ustar00rootroot00000000000000456 789 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.current000066400000000000000000000000071362162667600303760ustar00rootroot00000000000000111111 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.high000066400000000000000000000000041362162667600276300ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.low000066400000000000000000000000041362162667600275120ustar00rootroot00000000000000123 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.max000066400000000000000000000000041362162667600274760ustar00rootroot00000000000000max oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.min000066400000000000000000000000041362162667600274740ustar00rootroot00000000000000666 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.pressure000066400000000000000000000001641362162667600305700ustar00rootroot00000000000000some avg10=1.11 avg60=2.22 avg300=3.33 total=134829384400 full avg10=4.44 avg60=5.55 avg300=6.66 total=128544748770 oomd-0.3.1/src/oomd/fixtures/cgroup/workload.slice/service1.service/memory.swap.current000066400000000000000000000000021362162667600313420ustar00rootroot000000000000000 oomd-0.3.1/src/oomd/fixtures/fs_data/000077500000000000000000000000001362162667600174675ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/dir1/000077500000000000000000000000001362162667600203265ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/dir1/stuff000066400000000000000000000000331362162667600213740ustar00rootroot00000000000000hello world my good man 1 oomd-0.3.1/src/oomd/fixtures/fs_data/dir2/000077500000000000000000000000001362162667600203275ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/dir2/empty000066400000000000000000000000001362162667600213760ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/dir3/000077500000000000000000000000001362162667600203305ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/dir3/empty000066400000000000000000000000001362162667600213770ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/file1000066400000000000000000000000001362162667600204000ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/file2000066400000000000000000000000001362162667600204010ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/file3000066400000000000000000000000001362162667600204020ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/file4000066400000000000000000000000001362162667600204030ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/000077500000000000000000000000001362162667600212605ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/000077500000000000000000000000001362162667600222275ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/000077500000000000000000000000001362162667600231635ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/WAH/000077500000000000000000000000001362162667600236025ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/WAH/going/000077500000000000000000000000001362162667600247055ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/WAH/going/to/000077500000000000000000000000001362162667600253275ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/WAH/going/to/be/000077500000000000000000000000001362162667600257155ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/WAH/going/to/be/long/000077500000000000000000000000001362162667600266545ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/WAH/going/to/be/long/file000066400000000000000000000000001362162667600275040ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/is/000077500000000000000000000000001362162667600235765ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/is/going/000077500000000000000000000000001362162667600247015ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/is/going/to/000077500000000000000000000000001362162667600253235ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/is/going/to/be/000077500000000000000000000000001362162667600257115ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/is/going/to/be/long/000077500000000000000000000000001362162667600266505ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/is/going/to/be/long/file000066400000000000000000000000001362162667600275000ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/isNOT/000077500000000000000000000000001362162667600241575ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/isNOT/going/000077500000000000000000000000001362162667600252625ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/isNOT/going/to/000077500000000000000000000000001362162667600257045ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/isNOT/going/to/be/000077500000000000000000000000001362162667600262725ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/isNOT/going/to/be/long/000077500000000000000000000000001362162667600272315ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/fs_data/wildcard/this/path/isNOT/going/to/be/long/file000066400000000000000000000000001362162667600300610ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/oomd.json000066400000000000000000000043441362162667600177240ustar00rootroot00000000000000{ "rulesets": [ { "name": "my first ruleset", "drop-in": { "detectors": true, "actions": false, "disable-on-drop-in": true }, "silence-logs": "engine,plugins", "detectors": [ [ "group1", { "name": "pressure_rising_beyond", "args": { "cgroup": "workload.slice", "resource": "memory", "threshold": "5" } }, { "name": "pressure_rising_beyond", "args": { "cgroup": "system.slice", "resource": "memory", "threshold": "40" } } ] ], "actions": [ { "name": "kill_by_memory_size_or_growth", "args": { "cgroup": "system.slice" } } ] }, { "name": "low swap ruleset", "detectors": [ [ "my other group", { "name": "swap_free", "args": { "threshold_pct": "15" } } ] ], "actions": [ { "name": "kill_by_swap_usage", "args": { "cgroup": "system.slice" } }, { "name": "kill_by_swap_usage", "args": { "cgroup": "workload.slice/workload-wdb.slice" } }, { "name": "kill_by_swap_usage", "args": { "cgroup": "workload.slice/workload-tw.slice" } } ] } ] } oomd-0.3.1/src/oomd/fixtures/plugins/000077500000000000000000000000001362162667600175475ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/base_kill_plugin/000077500000000000000000000000001362162667600230525ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/base_kill_plugin/one_big/000077500000000000000000000000001362162667600244545ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/base_kill_plugin/one_big/cgroup.procs000077500000000000000000000001211362162667600270200ustar00rootroot000000000000001 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 oomd-0.3.1/src/oomd/fixtures/plugins/base_kill_plugin/one_big/child/000077500000000000000000000000001362162667600255375ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/base_kill_plugin/one_big/child/cgroup.procs000066400000000000000000000000051362162667600301010ustar00rootroot000000000000001234 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/000077500000000000000000000000001362162667600227135ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/000077500000000000000000000000001362162667600244735ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/cgroup1/000077500000000000000000000000001362162667600260535ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/cgroup1/cgroup.procs000066400000000000000000000000101362162667600304110ustar00rootroot00000000000000123 456 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/cgroup2/000077500000000000000000000000001362162667600260545ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/cgroup2/cgroup.procs000066400000000000000000000000041362162667600304150ustar00rootroot00000000000000789 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/cgroup3/000077500000000000000000000000001362162667600260555ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/one_high/cgroup3/cgroup.procs000066400000000000000000000000041362162667600304160ustar00rootroot00000000000000111 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/sibling/000077500000000000000000000000001362162667600243425ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/sibling/cgroup1/000077500000000000000000000000001362162667600257225ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_io_cost/sibling/cgroup1/cgroup.procs000066400000000000000000000000041362162667600302630ustar00rootroot00000000000000888 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/000077500000000000000000000000001362162667600257105ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/000077500000000000000000000000001362162667600300435ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/cgroup1/000077500000000000000000000000001362162667600314235ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/cgroup1/cgroup.procs000066400000000000000000000000101362162667600337610ustar00rootroot00000000000000123 456 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/cgroup2/000077500000000000000000000000001362162667600314245ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/cgroup2/cgroup.procs000066400000000000000000000000041362162667600337650ustar00rootroot00000000000000789 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/cgroup3/000077500000000000000000000000001362162667600314255ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/cgroup3/cgroup.procs000066400000000000000000000000041362162667600337660ustar00rootroot00000000000000111 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/growth_big/memory.pressure000066400000000000000000000001721362162667600331450ustar00rootroot00000000000000some avg10=99.99 avg60=99.99 avg300=99.99 total=134829384400 full avg10=99.99 avg60=99.99 avg300=99.99 total=128544748770 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/000077500000000000000000000000001362162667600273125ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/cgroup1/000077500000000000000000000000001362162667600306725ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/cgroup1/cgroup.procs000066400000000000000000000000101362162667600332300ustar00rootroot00000000000000123 456 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/cgroup2/000077500000000000000000000000001362162667600306735ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/cgroup2/cgroup.procs000066400000000000000000000000041362162667600332340ustar00rootroot00000000000000789 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/cgroup3/000077500000000000000000000000001362162667600306745ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/cgroup3/cgroup.procs000066400000000000000000000000041362162667600332350ustar00rootroot00000000000000111 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/one_big/memory.pressure000066400000000000000000000001721362162667600324140ustar00rootroot00000000000000some avg10=99.99 avg60=99.99 avg300=99.99 total=134829384400 full avg10=99.99 avg60=99.99 avg300=99.99 total=128544748770 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/sibling/000077500000000000000000000000001362162667600273375ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/sibling/cgroup1/000077500000000000000000000000001362162667600307175ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_memory_size_or_growth/sibling/cgroup1/cgroup.procs000066400000000000000000000000041362162667600332600ustar00rootroot00000000000000888 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/000077500000000000000000000000001362162667600231245ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/000077500000000000000000000000001362162667600247045ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/cgroup1/000077500000000000000000000000001362162667600262645ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/cgroup1/cgroup.procs000066400000000000000000000000101362162667600306220ustar00rootroot00000000000000123 456 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/cgroup2/000077500000000000000000000000001362162667600262655ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/cgroup2/cgroup.procs000066400000000000000000000000041362162667600306260ustar00rootroot00000000000000789 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/cgroup3/000077500000000000000000000000001362162667600262665ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/one_high/cgroup3/cgroup.procs000066400000000000000000000000041362162667600306270ustar00rootroot00000000000000111 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/sibling/000077500000000000000000000000001362162667600245535ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/sibling/cgroup1/000077500000000000000000000000001362162667600261335ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_pressure/sibling/cgroup1/cgroup.procs000066400000000000000000000000041362162667600304740ustar00rootroot00000000000000888 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/000077500000000000000000000000001362162667600234125ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/meminfo000066400000000000000000000000301362162667600247600ustar00rootroot00000000000000SwapTotal: 100 kB oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/000077500000000000000000000000001362162667600250145ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/cgroup1/000077500000000000000000000000001362162667600263745ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/cgroup1/cgroup.procs000066400000000000000000000000101362162667600307320ustar00rootroot00000000000000123 456 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/cgroup2/000077500000000000000000000000001362162667600263755ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/cgroup2/cgroup.procs000066400000000000000000000000041362162667600307360ustar00rootroot00000000000000789 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/cgroup3/000077500000000000000000000000001362162667600263765ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/one_big/cgroup3/cgroup.procs000066400000000000000000000000041362162667600307370ustar00rootroot00000000000000111 oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/sibling/000077500000000000000000000000001362162667600250415ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/sibling/cgroup1/000077500000000000000000000000001362162667600264215ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/kill_by_swap_usage/sibling/cgroup1/cgroup.procs000066400000000000000000000000041362162667600307620ustar00rootroot00000000000000555 oomd-0.3.1/src/oomd/fixtures/plugins/memory_above/000077500000000000000000000000001362162667600222335ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_above/meminfo000066400000000000000000000000331362162667600236040ustar00rootroot00000000000000MemTotal: 8388608 kB oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/000077500000000000000000000000001362162667600225535ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/multi_cgroup/000077500000000000000000000000001362162667600252645ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/multi_cgroup/cgroup1/000077500000000000000000000000001362162667600266445ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/multi_cgroup/cgroup1/memory.stat000066400000000000000000000010361362162667600310510ustar00rootroot00000000000000anon 1056014336 file 11877441536 kernel_stack 158109696 slab 1440972800 sock 13357056 shmem 12419072 file_mapped 1073233920 file_dirty 28925952 file_writeback 0 inactive_anon 12128256 active_anon 1006006272 inactive_file 6990409728 active_file 4981571584 unevictable 8138752 slab_reclaimable 815513600 slab_unreclaimable 625459200 pgfault 1225796781 pgmajfault 259380 pgrefill 0 pgscan 0 pgsteal 0 pgactivate 0 pgdeactivate 0 pglazyfree 0 pglazyfreed 0 workingset_refault 0 workingset_activate 0 workingset_restore 0 workingset_nodereclaim 0 oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/multi_cgroup/cgroup2/000077500000000000000000000000001362162667600266455ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/multi_cgroup/cgroup2/memory.stat000066400000000000000000000010361362162667600310520ustar00rootroot00000000000000anon 1056014336 file 11877441536 kernel_stack 158109696 slab 1440972800 sock 13357056 shmem 12419072 file_mapped 1073233920 file_dirty 28925952 file_writeback 0 inactive_anon 12128256 active_anon 1006006272 inactive_file 6990409728 active_file 4981571584 unevictable 8138752 slab_reclaimable 815513600 slab_unreclaimable 625459200 pgfault 1225796781 pgmajfault 259380 pgrefill 0 pgscan 5 pgsteal 0 pgactivate 0 pgdeactivate 0 pglazyfree 0 pglazyfreed 0 workingset_refault 0 workingset_activate 0 workingset_restore 0 workingset_nodereclaim 0 oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/single_cgroup/000077500000000000000000000000001362162667600254135ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/single_cgroup/cgroup1/000077500000000000000000000000001362162667600267735ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/plugins/memory_reclaim/single_cgroup/cgroup1/memory.stat000066400000000000000000000010361362162667600312000ustar00rootroot00000000000000anon 1056014336 file 11877441536 kernel_stack 158109696 slab 1440972800 sock 13357056 shmem 12419072 file_mapped 1073233920 file_dirty 28925952 file_writeback 0 inactive_anon 12128256 active_anon 1006006272 inactive_file 6990409728 active_file 4981571584 unevictable 8138752 slab_reclaimable 815513600 slab_unreclaimable 625459200 pgfault 1225796781 pgmajfault 259380 pgrefill 0 pgscan 5 pgsteal 0 pgactivate 0 pgdeactivate 0 pglazyfree 0 pglazyfreed 0 workingset_refault 0 workingset_activate 0 workingset_restore 0 workingset_nodereclaim 0 oomd-0.3.1/src/oomd/fixtures/proc/000077500000000000000000000000001362162667600170315ustar00rootroot00000000000000oomd-0.3.1/src/oomd/fixtures/proc/meminfo000066400000000000000000000025231362162667600204100ustar00rootroot00000000000000MemTotal: 58616708 kB MemFree: 2955848 kB MemAvailable: 49878948 kB Buffers: 4970160 kB Cached: 35172508 kB SwapCached: 0 kB Active: 27822776 kB Inactive: 19032824 kB Active(anon): 4024064 kB Inactive(anon): 2822876 kB Active(file): 23798712 kB Inactive(file): 16209948 kB Unevictable: 11684 kB Mlocked: 11668 kB SwapTotal: 2097148 kB SwapFree: 1097041 kB Dirty: 24576 kB Writeback: 0 kB AnonPages: 6725380 kB Mapped: 657992 kB Shmem: 130124 kB Slab: 8238080 kB SReclaimable: 7334616 kB SUnreclaim: 903464 kB KernelStack: 45984 kB PageTables: 75756 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 31405500 kB Committed_AS: 21265672 kB VmallocTotal: 34359738367 kB VmallocUsed: 0 kB VmallocChunk: 0 kB HardwareCorrupted: 0 kB AnonHugePages: 71680 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB CmaTotal: 0 kB CmaFree: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 4544368 kB DirectMap2M: 53127168 kB DirectMap1G: 4194304 kB oomd-0.3.1/src/oomd/fixtures/proc/mounts000066400000000000000000000040101362162667600202740ustar00rootroot00000000000000sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 devtmpfs /dev devtmpfs rw,seclabel,nosuid,size=58710812k,nr_inodes=14677703,mode=755 0 0 securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev 0 0 devpts /dev/pts devpts rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0 tmpfs /run tmpfs rw,seclabel,nosuid,nodev,mode=755 0 0 cgroup2 /sys/fs/cgroup cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0 pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0 bpf /sys/fs/bpf bpf rw,relatime,mode=700 0 0 configfs /sys/kernel/config configfs rw,relatime 0 0 /dev/vda3 / btrfs rw,seclabel,relatime,compress-force=zstd,discard,space_cache,subvolid=5,subvol=/ 0 0 selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0 systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=26,pgrp=1,timeout=0,minproto=5,maxproto=5,direct 0 0 hugetlbfs /dev/hugepages hugetlbfs rw,seclabel,relatime,pagesize=2M 0 0 debugfs /sys/kernel/debug debugfs rw,seclabel,relatime,mode=755 0 0 mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0 sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0 nfsd /proc/fs/nfsd nfsd rw,relatime 0 0 fusectl /sys/fs/fuse/connections fusectl rw,relatime 0 0 /dev/vda1 /boot ext4 rw,seclabel,relatime,data=ordered 0 0 none-tw-channel /run/facebook/tw_logging_channel tmpfs rw,seclabel,relatime,size=10240k,nr_inodes=4096 0 0 tracefs /sys/kernel/debug/tracing tracefs rw,seclabel,relatime 0 0 /etc/auto.home.override /home autofs rw,relatime,fd=5,pgrp=2182594,timeout=600,minproto=5,maxproto=5,indirect 0 0 binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc rw,relatime 0 0 tmpfs /run/user/30015 tmpfs rw,seclabel,nosuid,nodev,relatime,size=11744436k,mode=700,uid=30015,gid=30015 0 0 tmpfs /run/user/0 tmpfs rw,seclabel,nosuid,nodev,relatime,size=11744436k,mode=700 0 0 tmpfs /run/user/180401 tmpfs rw,seclabel,nosuid,nodev,relatime,size=11744436k,mode=700,uid=180401,gid=100 0 0 oomd-0.3.1/src/oomd/fixtures/proc/vmstat000066400000000000000000000000621362162667600202700ustar00rootroot00000000000000first_key 12345 second_key 678910 thirdkey 999999 oomd-0.3.1/src/oomd/include/000077500000000000000000000000001362162667600156405ustar00rootroot00000000000000oomd-0.3.1/src/oomd/include/Assert.cpp000066400000000000000000000027221362162667600176100ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include // backtrace(3) is a GNU extension #ifdef __GNU_LIBRARY__ #include #include #endif // __GNU_LIBRARY__ #include #include #include "oomd/include/Assert.h" void __bt() { #ifdef __GNU_LIBRARY__ static const int num = 100; void* buffer[num]; int n = backtrace(buffer, num); std::cerr << "Backtrace (most recent call first):" << std::endl; backtrace_symbols_fd(buffer, n, STDERR_FILENO); #endif // __GNU_LIBRARY__ } [[noreturn]] void __OCHECK_FAIL(const char* expr, const char* file, int line, const char* func) { std::cerr << file << ":" << line << ": " << func << ": Assertion \'" << expr << "\' failed." << std::endl << std::flush; __bt(); std::abort(); } oomd-0.3.1/src/oomd/include/Assert.h000066400000000000000000000020661362162667600172560ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once [[noreturn]] void __OCHECK_FAIL(const char* expr, const char* file, int line, const char* func); #define OCHECK(expr) \ (static_cast(expr) \ ? void(0) \ : __OCHECK_FAIL(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__)) #define OCHECK_EXCEPT(expr, exc) (static_cast(expr)) ? void(0) : throw exc oomd-0.3.1/src/oomd/include/AssertTest.cpp000066400000000000000000000026001362162667600204430ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "oomd/include/Assert.h" using namespace testing; TEST(OomdAssertTest, Death) { // Shouldn't die OCHECK(true); OCHECK(1 == 1); int x = 1; int y = x; OCHECK(x == y); ASSERT_DEATH(OCHECK(false), "Assertion.*failed"); } TEST(OomdAssertTest, ExprDeath) { ASSERT_DEATH(OCHECK(true == false), "Assertion.*failed"); } TEST(OomdAssertExceptionTest, Throws) { ASSERT_NO_THROW(OCHECK_EXCEPT(true, std::runtime_error("x"))); ASSERT_THROW( OCHECK_EXCEPT(false, std::runtime_error("x")), std::runtime_error); ASSERT_THROW(OCHECK_EXCEPT(false, std::exception()), std::exception); } oomd-0.3.1/src/oomd/include/CgroupPath.cpp000066400000000000000000000062221362162667600204220ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/include/CgroupPath.h" #include #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace Oomd { CgroupPath::CgroupPath( const std::string& cgroup_fs, const std::string& cgroup_path) : cgroup_fs_(cgroup_fs) { // Strip trailing '/' if (cgroup_fs_.at(cgroup_fs_.size() - 1) == '/' && cgroup_fs.size() > 1) { cgroup_fs_.pop_back(); } cgroup_path_ = Util::split(cgroup_path, '/'); recomputeReadCache(); } const std::string& CgroupPath::absolutePath() const { return absolute_cache_; } const std::string& CgroupPath::relativePath() const { return relative_cache_; } const std::vector& CgroupPath::relativePathParts() const { return cgroup_path_; } const std::string& CgroupPath::cgroupFs() const { return cgroup_fs_; } CgroupPath CgroupPath::getParent() const { if (this->isRoot()) { throw std::invalid_argument("Cannot get parent of root"); } CgroupPath parent(*this); parent.cgroup_path_.pop_back(); parent.recomputeReadCache(); return parent; } CgroupPath CgroupPath::getChild(const std::string& path) const { CgroupPath child(*this); auto pieces = Util::split(path, '/'); child.cgroup_path_.reserve(child.cgroup_path_.size() + pieces.size()); for (auto& piece : pieces) { child.cgroup_path_.emplace_back(std::move(piece)); } child.recomputeReadCache(); return child; } bool CgroupPath::operator==(const CgroupPath& other) const { return this->absolutePath() == other.absolutePath(); } bool CgroupPath::operator!=(const CgroupPath& other) const { return !operator==(other); } bool CgroupPath::isRoot() const { return cgroup_path_.size() == 0; } void CgroupPath::recomputeReadCache() { // Recreate relative_cache_ relative_cache_.clear(); size_t s = 0; for (size_t i = 0; i < cgroup_path_.size(); ++i) { s += cgroup_path_.at(i).size(); if (i == cgroup_path_.size() - 1) { break; } ++s; // for the path separator } relative_cache_.reserve(s); for (size_t i = 0; i < cgroup_path_.size(); ++i) { relative_cache_ += cgroup_path_.at(i); if (i == cgroup_path_.size() - 1) { break; } relative_cache_ += '/'; } // Recreate absolute_cache_ absolute_cache_.clear(); absolute_cache_.reserve(cgroup_fs_.size() + 1 + relative_cache_.size()); absolute_cache_ += cgroup_fs_; if (relative_cache_.size()) { absolute_cache_ += '/'; absolute_cache_ += relative_cache_; } } } // namespace Oomd oomd-0.3.1/src/oomd/include/CgroupPath.h000066400000000000000000000040371362162667600200710ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include namespace Oomd { class CgroupPath { public: CgroupPath(const std::string& cgroup_fs, const std::string& cgroup_path); ~CgroupPath() = default; CgroupPath(const CgroupPath& other) = default; CgroupPath(CgroupPath&& other) = default; CgroupPath& operator=(const CgroupPath& other) = default; CgroupPath& operator=(CgroupPath&& other) = default; static void setCgroupFs(const std::string& cgroup_fs); const std::string& absolutePath() const; // cgroup path without the cgroup fs const std::string& relativePath() const; const std::vector& relativePathParts() const; const std::string& cgroupFs() const; CgroupPath getParent() const; CgroupPath getChild(const std::string& path) const; bool operator==(const CgroupPath& other) const; bool operator!=(const CgroupPath& other) const; // Do we represent the root cgroup? bool isRoot() const; private: void recomputeReadCache(); std::string cgroup_fs_; std::vector cgroup_path_; std::string absolute_cache_; std::string relative_cache_; }; } // namespace Oomd namespace std { template <> struct hash { size_t operator()(const Oomd::CgroupPath& path) const { return hash()(path.absolutePath()); } }; } // namespace std oomd-0.3.1/src/oomd/include/CgroupPathTest.cpp000066400000000000000000000066031362162667600212650ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "oomd/include/CgroupPath.h" using namespace testing; using namespace Oomd; TEST(CgroupPathTest, ConstructorsTest) { CgroupPath path1("/sys/fs/cgroup", "myservice"); CgroupPath path2(path1); EXPECT_EQ(path1.absolutePath(), path2.absolutePath()); path1 = path2; EXPECT_EQ(path1.absolutePath(), path2.absolutePath()); CgroupPath path3("/sys/fs/cgroup", "myservice"); CgroupPath path4(std::move(path3)); EXPECT_EQ(path4.absolutePath(), "/sys/fs/cgroup/myservice"); CgroupPath path5 = std::move(path4); EXPECT_EQ(path5.absolutePath(), "/sys/fs/cgroup/myservice"); // Test root token maps to root cgroup CgroupPath path("/sys/fs/cgroup", "/"); EXPECT_TRUE(path.isRoot()); } TEST(CgroupPathTest, GettersTest) { CgroupPath path("/sys/fs/cgroup", "system.slice/myservice.slice"); EXPECT_EQ(path.absolutePath(), "/sys/fs/cgroup/system.slice/myservice.slice"); EXPECT_EQ(path.relativePath(), "system.slice/myservice.slice"); EXPECT_EQ(path.cgroupFs(), "/sys/fs/cgroup"); ASSERT_EQ(path.relativePathParts().size(), 2); EXPECT_EQ(path.relativePathParts()[0], "system.slice"); EXPECT_EQ(path.relativePathParts()[1], "myservice.slice"); CgroupPath pathEmpty("/sys/fs/cgroup", ""); EXPECT_EQ(pathEmpty.relativePath().size(), 0); EXPECT_EQ(pathEmpty.absolutePath(), "/sys/fs/cgroup"); CgroupPath pathSame("/sys/fs/cgroup", "////////"); EXPECT_EQ(pathSame.relativePath().size(), 0); EXPECT_EQ(pathSame.absolutePath(), "/sys/fs/cgroup"); } TEST(CgroupPathTest, ModifiersTest) { CgroupPath path("/sys/fs/cgroup", "system.slice"); path = path.getChild("myservice"); path = path.getChild("/one/"); path = path.getChild("two/"); path = path.getChild("three"); EXPECT_EQ(path.relativePath(), "system.slice/myservice/one/two/three"); EXPECT_EQ( path.getParent().getParent().relativePath(), "system.slice/myservice/one"); CgroupPath root("/sys/fs/cgroup", ""); EXPECT_THROW(root.getParent(), std::invalid_argument); } TEST(CgroupPathTest, ComparisonsTest) { CgroupPath path1("/sys/fs/cgroup", "system.slice"); CgroupPath path2("/sys/fs/cgroup", "system.slice"); EXPECT_EQ(path1, path2); CgroupPath path3("/sys/fs/cgroup", "asdf.slice"); EXPECT_NE(path1, path3); EXPECT_FALSE(path3.isRoot()); EXPECT_TRUE(path3.getParent().isRoot()); } TEST(CgroupPathTest, HashTest) { std::unordered_map m; CgroupPath p1("/sys/fs/cgroup", "system.slice"); CgroupPath p2("/sys/fs/cgroup", "workload.slice"); m[p1] = 1; m[p2] = 2; EXPECT_EQ(m[p1], 1); EXPECT_EQ(m[p2], 2); CgroupPath p3("/sys/fs/cgroup", "system.slice"); EXPECT_EQ(m[p3], 1); } oomd-0.3.1/src/oomd/include/CoreStats.h000066400000000000000000000023231362162667600177200ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include namespace Oomd { class CoreStats { public: static constexpr auto kKillsKey = "oomd.kills"; static constexpr auto kNumDropInAdds = "oomd.dropin.added"; static constexpr auto kNumDropInFired = "oomd.dropin.fired"; // List of all the stats keys. Useful for operations that need to know // all the available core keys. static constexpr std::array kAllKeys = { kKillsKey, kNumDropInAdds, kNumDropInFired, }; }; } // namespace Oomd oomd-0.3.1/src/oomd/include/Defines.h000066400000000000000000000013771362162667600173760ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #define EXIT_CANT_RECOVER 3 oomd-0.3.1/src/oomd/include/Types.h000066400000000000000000000045431362162667600171230ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include namespace Oomd { enum struct ResourceType { MEMORY, IO, }; enum struct DeviceType { HDD, SSD, }; struct DeviceIOStat { std::string dev_id{""}; int64_t rbytes{0}; int64_t wbytes{0}; int64_t rios{0}; int64_t wios{0}; int64_t dbytes{0}; int64_t dios{0}; }; using IOStat = std::vector; struct IOCostCoeffs { double read_iops{0}; double readbw{0}; double write_iops{0}; double writebw{0}; double trim_iops{0}; double trimbw{0}; }; struct ResourcePressure { float sec_10{0}; float sec_60{0}; float sec_600{0}; std::optional total{std::nullopt}; }; // If you update this class with something that could be valuable to know // when debugging, please remember to update OomdContext::dumpOomdContext // as well. class CgroupContext { public: ResourcePressure pressure; ResourcePressure io_pressure; int64_t current_usage{0}; int64_t average_usage{0}; int64_t memory_low{0}; int64_t memory_protection{0}; int64_t swap_usage{0}; int64_t anon_usage{0}; int64_t memory_min{0}; int64_t memory_high{0}; int64_t memory_high_tmp{0}; int64_t memory_max{0}; float memory_scale{1}; int64_t memory_adj{0}; double io_cost_cumulative{0}; // Dot product between io stat and coeffs double io_cost_rate{0}; // change of cumulative divided by interval in seconds int64_t nr_dying_descendants{0}; int64_t effective_usage() const { return current_usage * memory_scale - memory_protection + memory_adj; } }; struct SystemContext { uint64_t swaptotal{0}; uint64_t swapused{0}; }; } // namespace Oomd oomd-0.3.1/src/oomd/include/Version.h.in000066400000000000000000000000561362162667600200440ustar00rootroot00000000000000#pragma once #define GIT_VERSION "@VCS_TAG@" oomd-0.3.1/src/oomd/plugins/000077500000000000000000000000001362162667600156765ustar00rootroot00000000000000oomd-0.3.1/src/oomd/plugins/AdjustCgroup.cpp000066400000000000000000000061101362162667600210120ustar00rootroot00000000000000/* * Copyright (C) 2019-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/AdjustCgroup.h" #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup/"; namespace Oomd { REGISTER_PLUGIN(adjust_cgroup, AdjustCgroup::create); int AdjustCgroup::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("memory_scale") != args.end()) { memory_scale_set_ = true; memory_scale_ = std::stof(args.at("memory_scale")); } if (args.find("memory") != args.end()) { memory_adj_set_ = true; if (Util::parseSize(args.at("memory"), &memory_adj_) < 0) { OLOG << "Invalid size '" << args.at("memory") << "'"; return 1; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } Engine::PluginRet AdjustCgroup::run(OomdContext& ctx) { std::string current_cgroup; for (const auto& cgroup : cgroups_) { try { auto& cgroup_ctx = ctx.getMutableCgroupContext(cgroup); if (memory_scale_set_) { cgroup_ctx.memory_scale = memory_scale_; if (debug_) { OLOG << "cgroup \"" << cgroup.relativePath() << "\" " << "memory_scale=" << cgroup_ctx.memory_scale; } } if (memory_adj_set_) { cgroup_ctx.memory_adj = memory_adj_; if (debug_) { OLOG << "cgroup \"" << cgroup.relativePath() << "\" " << "memory_adj=" << cgroup_ctx.memory_adj; } } } catch (const std::exception& ex) { if (debug_) { OLOG << "Failed to get cgroup \"" << cgroup.relativePath() << "\" " << "context: " << ex.what(); } continue; } } return Engine::PluginRet::CONTINUE; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/AdjustCgroup.h000066400000000000000000000024771362162667600204730ustar00rootroot00000000000000/* * Copyright (C) 2019-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/engine/BasePlugin.h" #include #include namespace Oomd { class AdjustCgroup : public Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static AdjustCgroup* create() { return new AdjustCgroup(); } private: std::unordered_set cgroups_; bool memory_scale_set_{false}; float memory_scale_; bool memory_adj_set_{false}; int64_t memory_adj_; bool debug_{false}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/BaseKillPlugin.cpp000066400000000000000000000126641362162667600212600ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/BaseKillPlugin.h" #include #include #include #include #include #include "oomd/Log.h" #include "oomd/Stats.h" #include "oomd/include/CoreStats.h" #include "oomd/util/Fs.h" static auto constexpr kOomdKillInitiationXattr = "trusted.oomd_ooms"; static auto constexpr kOomdKillCompletionXattr = "trusted.oomd_kill"; namespace Oomd { BaseKillPlugin::BaseKillPlugin() { /* * Initializes kKillsKey in stats for immediate reporting, * rather than waiting for first occurrence */ Oomd::setStat(CoreStats::kKillsKey, 0); } int BaseKillPlugin::getAndTryToKillPids( const std::string& path, bool recursive, size_t stream_size) { int nr_killed = 0; std::ifstream f(path + "/" + Fs::kProcsFile, std::ios::in); if (!f.is_open()) { OLOG << "Unable to open " << path; return 0; } while (!f.eof()) { std::string s; std::vector pids; while (std::getline(f, s)) { pids.push_back(std::stoi(s)); if (pids.size() == stream_size) { break; } } if (f.bad()) { OLOG << "Error while processing file " << path; } nr_killed += tryToKillPids(pids); } if (recursive) { auto de = Fs::readDir(path, Fs::DE_DIR); for (const auto& dir : de.dirs) { nr_killed += getAndTryToKillPids(path + "/" + dir, true, stream_size); } } return nr_killed; } bool BaseKillPlugin::tryToKillCgroup( const std::string& cgroup_path, bool recursive, bool dry) { using namespace std::chrono_literals; int last_nr_killed = 0; int nr_killed = 0; int tries = 10; static constexpr size_t stream_size = 20; if (dry) { OLOG << "OOMD: In dry-run mode; would have tried to kill " << cgroup_path; return true; } OLOG << "Trying to kill " << cgroup_path; reportKillInitiationToXattr(cgroup_path); while (tries--) { nr_killed += getAndTryToKillPids(cgroup_path, recursive, stream_size); if (nr_killed == last_nr_killed) { break; } // Give it a breather before killing again // // Don't sleep after the first round of kills b/c the majority of the // time the sleep isn't necessary. The system responds fast enough. if (last_nr_killed) { std::this_thread::sleep_for(1s); } last_nr_killed = nr_killed; } reportKillCompletionToXattr(cgroup_path, nr_killed); return nr_killed > 0; } int BaseKillPlugin::tryToKillPids(const std::vector& pids) { std::ostringstream buf; int nr_killed = 0; for (int pid : pids) { auto comm_path = std::string("/proc/") + std::to_string(pid) + "/comm"; auto comm = Fs::readFileByLine(comm_path); if (comm.size()) { buf << " " << pid << "(" << comm[0] << ")"; } else { buf << " " << pid; } if (::kill(static_cast(pid), SIGKILL) == 0) { nr_killed++; } else { buf << "[E" << errno << "]"; } } if (buf.tellp()) { OLOG << "Killed " << nr_killed << ":" << buf.str(); } return nr_killed; } void BaseKillPlugin::reportKillInitiationToXattr( const std::string& cgroup_path) { auto prev_xattr_str = Fs::getxattr(cgroup_path, kOomdKillInitiationXattr); const int prev_xattr = std::stoi(prev_xattr_str != "" ? prev_xattr_str : "0"); std::string new_xattr_str = std::to_string(prev_xattr + 1); if (Fs::setxattr(cgroup_path, kOomdKillInitiationXattr, new_xattr_str)) { OLOG << "Set xattr " << kOomdKillInitiationXattr << "=" << new_xattr_str << " on " << cgroup_path; } } void BaseKillPlugin::reportKillCompletionToXattr( const std::string& cgroup_path, int num_procs_killed) { auto prev_xattr_str = Fs::getxattr(cgroup_path, kOomdKillCompletionXattr); const int prev_xattr = std::stoi(prev_xattr_str != "" ? prev_xattr_str : "0"); std::string new_xattr_str = std::to_string(prev_xattr + num_procs_killed); if (Fs::setxattr(cgroup_path, kOomdKillCompletionXattr, new_xattr_str)) { OLOG << "Set xattr " << kOomdKillCompletionXattr << "=" << new_xattr_str << " on " << cgroup_path; } } void BaseKillPlugin::logKill( const CgroupPath& killed_cgroup, const CgroupContext& context, const ActionContext& action_context, bool dry) const { std::ostringstream oss; oss << std::setprecision(2) << std::fixed; oss << context.pressure.sec_10 << " " << context.pressure.sec_60 << " " << context.pressure.sec_600 << " " << killed_cgroup.relativePath() << " " << context.current_usage << " " << "ruleset:[" << action_context.ruleset << "] " << "detectorgroup:[" << action_context.detectorgroup << "] " << "killer:" << (dry ? "(dry)" : "") << getName() << " v2"; Oomd::incrementStat(CoreStats::kKillsKey, 1); OOMD_KMSG_LOG(oss.str(), "oomd kill"); } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/BaseKillPlugin.h000066400000000000000000000047621362162667600207250ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/engine/BasePlugin.h" namespace Oomd { /* * This abstract base class provides an overridable set of methods that * enables reuse of kill mechanism code. All plugins that kill processes * need code that can traverse a cgroup directory and start killing PIDs. * There is no reason for every plugin to rewrite that code. * * Ideally all killing plugins should inherit from this base class. * If customized behavior is desired, then simply override the relevant * methods. */ class BaseKillPlugin : public Oomd::Engine::BasePlugin { protected: BaseKillPlugin(); /* * Kills a cgroup * * @param cgroup_path is the absolute path to a cgroup (eg /sys/fs/cgroup/...) * @param recursive, if true, recursively kills every process (ie children * cgroups) starting at @param cgroup_path * @param dry sets whether or not we should actually issue SIGKILLs */ virtual bool tryToKillCgroup(const std::string& cgroup_path, bool recursive, bool dry); /* * Sends SIGKILL to every PID in @param procs */ virtual int tryToKillPids(const std::vector& procs); virtual void reportKillInitiationToXattr(const std::string& cgroup_path); /* * Sets the "trusted.oomd_kill" extended attribute key to @param * num_procs_killed on @param cgroup_path */ virtual void reportKillCompletionToXattr( const std::string& cgroup_path, int num_procs_killed); /* * Logs a structured kill message to kmsg and stderr */ virtual void logKill( const CgroupPath& killed_group, const CgroupContext& context, const ActionContext& action_context, bool dry = false) const; private: virtual int getAndTryToKillPids( const std::string& path, bool recursive, size_t stream_size); }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/ContinuePlugin.cpp000066400000000000000000000015731362162667600213530ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/ContinuePlugin.h" #include "oomd/PluginRegistry.h" namespace Oomd { REGISTER_PLUGIN(continue, ContinuePlugin::create); } // namespace Oomd oomd-0.3.1/src/oomd/plugins/ContinuePlugin.h000066400000000000000000000023121362162667600210100ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/engine/BasePlugin.h" namespace Oomd { class ContinuePlugin : public Engine::BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& /* unused */) override { return 0; } Engine::PluginRet run(OomdContext& /* unused */) override { return Engine::PluginRet::CONTINUE; } static ContinuePlugin* create() { return new ContinuePlugin(); } ~ContinuePlugin() = default; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/CorePluginsTest.cpp000066400000000000000000001510061362162667600214770ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include "oomd/OomdContext.h" #include "oomd/PluginRegistry.h" #include "oomd/engine/BasePlugin.h" #include "oomd/plugins/BaseKillPlugin.h" #include "oomd/plugins/KillIOCost.h" #include "oomd/plugins/KillMemoryGrowth.h" #include "oomd/plugins/KillPressure.h" #include "oomd/plugins/KillSwapUsage.h" using namespace Oomd; using namespace testing; namespace { std::unique_ptr createPlugin(const std::string& name) { return std::unique_ptr( Oomd::getPluginRegistry().create(name)); } } // namespace namespace Oomd { class BaseKillPluginMock : public BaseKillPlugin { public: int tryToKillPids(const std::vector& pids) override { int ret = 0; killed.reserve(pids.size()); for (int pid : pids) { if (killed.find(pid) == killed.end()) { killed.emplace(pid); ++ret; } } return ret; } std::unordered_set killed; }; /* * Use this concrete class to test BaseKillPlugin methods */ class BaseKillPluginShim : public BaseKillPluginMock { public: int init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& /* unused */) override { return 0; } Engine::PluginRet run(OomdContext& /* unused */) override { return Engine::PluginRet::CONTINUE; } bool tryToKillCgroupShim( const std::string& cgroup_path, bool recursive, bool dry) { return BaseKillPluginMock::tryToKillCgroup(cgroup_path, recursive, dry); } }; } // namespace Oomd TEST(AdjustCgroupPlugin, AdjustCgroupMemory) { auto plugin = createPlugin("adjust_cgroup"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "adjust_cgroup"; args["memory_scale"] = "1.5"; args["memory"] = "-8M"; args["debug"] = "1"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; auto cgroup_path = CgroupPath(args["cgroup_fs"], "adjust_cgroup"); ctx.setCgroupContext( cgroup_path, CgroupContext{.current_usage = 64 << 20, .memory_protection = 16 << 20}); auto& cgroup_ctx = ctx.getCgroupContext(cgroup_path); EXPECT_EQ(cgroup_ctx.effective_usage(), (64 << 20) - (16 << 20)); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); EXPECT_EQ( cgroup_ctx.effective_usage(), (int64_t)((64 << 20) * 1.5 - (16 << 20) - (8 << 20))); } TEST(BaseKillPlugin, TryToKillCgroupKillsNonRecursive) { BaseKillPluginShim plugin; EXPECT_EQ( plugin.tryToKillCgroupShim( "oomd/fixtures/plugins/base_kill_plugin/one_big", false, false), true); int expected_total = 0; for (int i = 1; i <= 30; ++i) { expected_total += i; } int received_total = 0; for (int i : plugin.killed) { received_total += i; } EXPECT_EQ(expected_total, received_total); } TEST(BaseKillPlugin, TryToKillCgroupKillsRecursive) { BaseKillPluginShim plugin; EXPECT_EQ( plugin.tryToKillCgroupShim( "oomd/fixtures/plugins/base_kill_plugin/one_big", true, false), true); int expected_total = 0; for (int i = 1; i <= 30; ++i) { expected_total += i; } expected_total += 1234; int received_total = 0; for (int i : plugin.killed) { received_total += i; } EXPECT_EQ(expected_total, received_total); } TEST(BaseKillPlugin, RemoveSiblingCgroups) { OomdContext ctx; ctx.setCgroupContext( CgroupPath("/", "some/made_up/cgroup/path/here"), CgroupContext{}); ctx.setCgroupContext( CgroupPath("/", "some/other/cgroup/path/here"), CgroupContext{}); ctx.setCgroupContext( CgroupPath("/", "notavalidcgrouppath/here"), CgroupContext{}); ctx.setCgroupContext(CgroupPath("/", "XXXXXXXX/here"), CgroupContext{}); auto vec = ctx.reverseSort(); auto plugin = std::make_shared(); ASSERT_NE(plugin, nullptr); // Test wildcard support first OomdContext::removeSiblingCgroups( {CgroupPath("/", "some/*/cgroup/path/*")}, vec); ASSERT_EQ(vec.size(), 2); EXPECT_TRUE(std::any_of(vec.begin(), vec.end(), [&](const auto& pair) { return pair.first.relativePath() == "some/made_up/cgroup/path/here"; })); EXPECT_TRUE(std::any_of(vec.begin(), vec.end(), [&](const auto& pair) { return pair.first.relativePath() == "some/other/cgroup/path/here"; })); // Now test non-wildcard OomdContext::removeSiblingCgroups( {CgroupPath("/", "some/other/cgroup/path/*")}, vec); ASSERT_EQ(vec.size(), 1); EXPECT_EQ(vec[0].first.relativePath(), "some/other/cgroup/path/here"); } TEST(BaseKillPlugin, RemoveSiblingCgroupsMultiple) { OomdContext ctx; ctx.setCgroupContext( CgroupPath("/", "some/made_up/cgroup/path/here"), CgroupContext{}); ctx.setCgroupContext( CgroupPath("/", "some/other/cgroup/path/here"), CgroupContext{}); ctx.setCgroupContext( CgroupPath("/", "notavalidcgrouppath/here"), CgroupContext{}); ctx.setCgroupContext(CgroupPath("/", "XXXXXXXX/here"), CgroupContext{}); auto vec = ctx.reverseSort(); auto plugin = std::make_shared(); ASSERT_NE(plugin, nullptr); OomdContext::removeSiblingCgroups( {CgroupPath("/", "some/made_up/cgroup/path/*"), CgroupPath("/", "some/other/cgroup/path/*")}, vec); ASSERT_EQ(vec.size(), 2); EXPECT_TRUE(std::any_of(vec.begin(), vec.end(), [&](const auto& pair) { return pair.first.relativePath() == "some/made_up/cgroup/path/here"; })); EXPECT_TRUE(std::any_of(vec.begin(), vec.end(), [&](const auto& pair) { return pair.first.relativePath() == "some/other/cgroup/path/here"; })); } TEST(PresureRisingBeyond, DetectsHighMemPressure) { auto plugin = createPlugin("pressure_rising_beyond"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_rising_beyond"; args["cgroup"] = "high_pressure"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; args["fast_fall_ratio"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_pressure"), CgroupContext{.pressure = ResourcePressure{ .sec_10 = 99.99, .sec_60 = 99.99, .sec_600 = 99.99}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(PresureRisingBeyond, NoDetectLowMemPressure) { auto plugin = createPlugin("pressure_rising_beyond"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_rising_beyond"; args["cgroup"] = "low_pressure"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; args["fast_fall_ratio"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_pressure"), CgroupContext{ .pressure = ResourcePressure{.sec_10 = 1.11, .sec_60 = 1.11, .sec_600 = 1.11}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(PresureRisingBeyond, DetectsHighMemPressureMultiCgroup) { auto plugin = createPlugin("pressure_rising_beyond"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_rising_beyond"; args["cgroup"] = "low_pressure,high_pressure"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; args["fast_fall_ratio"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_pressure"), CgroupContext{.pressure = ResourcePressure{ .sec_10 = 99.99, .sec_60 = 99.99, .sec_600 = 99.99}, .current_usage = 987654321}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_pressure"), CgroupContext{ .pressure = ResourcePressure{.sec_10 = 1.11, .sec_60 = 1.11, .sec_600 = 1.11}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(PresureRisingBeyond, DetectsHighMemPressureWildcard) { auto plugin = createPlugin("pressure_rising_beyond"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_rising_beyond"; args["cgroup"] = "*_*"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; args["fast_fall_ratio"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_pressure"), CgroupContext{.pressure = ResourcePressure{ .sec_10 = 99.99, .sec_60 = 99.99, .sec_600 = 99.99}, .current_usage = 987654321}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_pressure"), CgroupContext{ .pressure = ResourcePressure{.sec_10 = 1.11, .sec_60 = 1.11, .sec_600 = 1.11}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(PressureAbove, DetectsHighMemPressure) { auto plugin = createPlugin("pressure_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_above"; args["cgroup"] = "high_pressure"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_pressure"), CgroupContext{.pressure = ResourcePressure{ .sec_10 = 99.99, .sec_60 = 99.99, .sec_600 = 99.99}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(PressureAbove, NoDetectLowMemPressure) { auto plugin = createPlugin("pressure_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_above"; args["cgroup"] = "low_pressure"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_pressure"), CgroupContext{ .pressure = ResourcePressure{.sec_10 = 1.11, .sec_60 = 1.11, .sec_600 = 1.11}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(PressureAbove, DetectsHighMemPressureMultiCgroup) { auto plugin = createPlugin("pressure_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_above"; args["cgroup"] = "high_pressure,low_pressure"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_pressure"), CgroupContext{.pressure = ResourcePressure{ .sec_10 = 99.99, .sec_60 = 99.99, .sec_600 = 99.99}, .current_usage = 987654321}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_pressure"), CgroupContext{ .pressure = ResourcePressure{.sec_10 = 1.11, .sec_60 = 1.11, .sec_600 = 1.11}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(PressureAbove, DetectsHighMemPressureWildcard) { auto plugin = createPlugin("pressure_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/pressure_above"; args["cgroup"] = "*"; args["resource"] = "memory"; args["threshold"] = "80"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_pressure"), CgroupContext{.pressure = ResourcePressure{ .sec_10 = 99.99, .sec_60 = 99.99, .sec_600 = 99.99}, .current_usage = 987654321}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_pressure"), CgroupContext{ .pressure = ResourcePressure{.sec_10 = 1.11, .sec_60 = 1.11, .sec_600 = 1.11}, .current_usage = 987654321}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, DetectsHighMemUsage) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "high_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "1536M"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{.current_usage = 2147483648}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, NoDetectLowMemUsage) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "low_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "1536M"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{.current_usage = 1073741824}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(MemoryAbove, DetectsHighMemUsageCompat) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "high_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "1536"; // Should be interpreted as MB args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{.current_usage = 2147483648}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, NoDetectLowMemUsageCompat) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "low_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "1536"; // Should be interpreted as MB args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{.current_usage = 1073741824}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(MemoryAbove, DetectsHighMemUsagePercent) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "high_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "10%"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{.current_usage = 2147483648}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, NoDetectLowMemUsageMultiple) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "low_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "1536M"; args["duration"] = "0"; args["debug"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{.current_usage = 1073741824}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{.current_usage = 2147483648}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(MemoryAbove, DetectsHighMemUsageMultiple) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "high_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "1536M"; args["duration"] = "0"; args["debug"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{.current_usage = 1073741824}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{.current_usage = 2147483648}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, NoDetectLowMemUsagePercent) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "low_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold"] = "80%"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{.current_usage = 1073741824}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(MemoryAbove, DetectsHighAnonUsage) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "high_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold_anon"] = "1536M"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{ .swap_usage = 20, .anon_usage = 2147483648, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, NoDetectLowAnonUsage) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "low_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold_anon"] = "1536M"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{ .swap_usage = 20, .anon_usage = 1073741824, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(MemoryAbove, DetectsHighAnonUsageIgnoreLowMemUsage) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "high_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold_anon"] = "1536M"; args["threshold"] = "1536M"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "high_memory"), CgroupContext{ .current_usage = 1073741824, .swap_usage = 20, .anon_usage = 2147483648, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryAbove, NoDetectLowAnonUsageIgnoreHighMemUsage) { auto plugin = createPlugin("memory_above"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_above"; args["cgroup"] = "low_memory"; args["meminfo_location"] = "oomd/fixtures/plugins/memory_above/meminfo"; args["threshold_anon"] = "1536M"; args["threshold"] = "1536M"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "low_memory"), CgroupContext{ .current_usage = 2147483648, .swap_usage = 20, .anon_usage = 1073741824, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(MemoryReclaim, SingleCgroupReclaimSuccess) { auto plugin = createPlugin("memory_reclaim"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_reclaim/single_cgroup"; args["cgroup"] = "cgroup1"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext(CgroupPath(args["cgroup_fs"], "cgroup1"), {}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(MemoryReclaim, MultiCgroupReclaimSuccess) { auto plugin = createPlugin("memory_reclaim"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/memory_reclaim/multi_cgroup"; args["cgroup"] = "cgroup1,cgroup2"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext(CgroupPath(args["cgroup_fs"], "cgroup1"), {}); ctx.setCgroupContext(CgroupPath(args["cgroup_fs"], "cgroup2"), {}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(SwapFree, LowSwap) { auto plugin = createPlugin("swap_free"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["threshold_pct"] = "20"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 0); OomdContext ctx; SystemContext system_ctx; system_ctx.swaptotal = static_cast(20971512) * 1024; system_ctx.swapused = static_cast(20971440) * 1024; ctx.setSystemContext(system_ctx); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(SwapFree, EnoughSwap) { auto plugin = createPlugin("swap_free"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["threshold_pct"] = "20"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 0); OomdContext ctx; SystemContext system_ctx; system_ctx.swaptotal = static_cast(20971512) * 1024; system_ctx.swapused = static_cast(3310136) * 1024; ctx.setSystemContext(system_ctx); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(SwapFree, SwapOff) { auto plugin = createPlugin("swap_free"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["threshold_pct"] = "20"; args["duration"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 0); OomdContext ctx; EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(Exists, Exists) { auto plugin = createPlugin("exists"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "cgroup_A,cgroup_B,cgroup_C"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 3); OomdContext ctx; auto cgroup_path_C = CgroupPath(args["cgroup_fs"], "cgroup_C"); auto cgroup_path_D = CgroupPath(args["cgroup_fs"], "cgroup_D"); ctx.setCgroupContext(cgroup_path_D, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); ctx.setCgroupContext(cgroup_path_C, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(Exists, NotExists) { auto plugin = createPlugin("exists"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "cgroup_A,cgroup_B,cgroup_C"; args["negate"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 3); OomdContext ctx; auto cgroup_path_C = CgroupPath(args["cgroup_fs"], "cgroup_C"); auto cgroup_path_D = CgroupPath(args["cgroup_fs"], "cgroup_D"); ctx.setCgroupContext(cgroup_path_D, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); ctx.setCgroupContext(cgroup_path_C, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(KillIOCost, KillsHighestIOCost) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_io_cost"; args["cgroup"] = "one_high/*"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup1"), CgroupContext{.io_cost_cumulative = 10000, .io_cost_rate = 10}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup2"), CgroupContext{.io_cost_cumulative = 5000, .io_cost_rate = 30}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup3"), CgroupContext{.io_cost_cumulative = 6000, .io_cost_rate = 50}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.io_cost_cumulative = 20000, .io_cost_rate = 100}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(111)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(789))); EXPECT_THAT(plugin->killed, Not(Contains(888))); } TEST(KillIOCost, KillsHighestIOCostMultiCgroup) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_io_cost"; args["cgroup"] = "one_high/*,sibling/*"; args["resource"] = "io"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup1"), CgroupContext{.io_cost_cumulative = 10000, .io_cost_rate = 10}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup2"), CgroupContext{.io_cost_cumulative = 5000, .io_cost_rate = 30}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup3"), CgroupContext{.io_cost_cumulative = 6000, .io_cost_rate = 50}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.io_cost_cumulative = 20000, .io_cost_rate = 100}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(888)); EXPECT_THAT(plugin->killed, Not(Contains(111))); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(789))); } TEST(KillIOCost, DoesntKillsHighestIOCostDry) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_pressure"; args["cgroup"] = "one_high/*"; args["resource"] = "io"; args["post_action_delay"] = "0"; args["dry"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup1"), CgroupContext{.io_cost_cumulative = 10000, .io_cost_rate = 10}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup2"), CgroupContext{.io_cost_cumulative = 5000, .io_cost_rate = 30}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup3"), CgroupContext{.io_cost_cumulative = 6000, .io_cost_rate = 50}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.io_cost_cumulative = 20000, .io_cost_rate = 100}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_EQ(plugin->killed.size(), 0); } TEST(Exists, ExistsWildcard) { auto plugin = createPlugin("exists"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "cgroup_PREFIX*"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; auto cgroup_path_notok = CgroupPath(args["cgroup_fs"], "cgroup_SOMETHING"); auto cgroup_path_ok = CgroupPath(args["cgroup_fs"], "cgroup_PREFIXhere"); ctx.setCgroupContext(cgroup_path_notok, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); ctx.setCgroupContext(cgroup_path_ok, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(Exists, NotExistsWildcard) { auto plugin = createPlugin("exists"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "cgroup_PREFIX*"; args["negate"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; auto cgroup_path_notok = CgroupPath(args["cgroup_fs"], "cgroup_SOMETHING"); auto cgroup_path_ok = CgroupPath(args["cgroup_fs"], "cgroup_PREFIXhere"); ctx.setCgroupContext(cgroup_path_notok, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); ctx.setCgroupContext(cgroup_path_ok, CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(NrDyingDescendants, SingleCgroupLte) { auto plugin = createPlugin("nr_dying_descendants"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "cg"; args["debug"] = "true"; args["lte"] = "true"; args["count"] = "100"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "cg"), CgroupContext{.nr_dying_descendants = 123}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "cg"), CgroupContext{.nr_dying_descendants = 90}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(NrDyingDescendants, SingleCgroupGt) { auto plugin = createPlugin("nr_dying_descendants"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "cg"; args["debug"] = "true"; args["lte"] = "false"; args["count"] = "100"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "cg"), CgroupContext{.nr_dying_descendants = 123}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "cg"), CgroupContext{.nr_dying_descendants = 90}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); } TEST(NrDyingDescendants, RootCgroup) { auto plugin = createPlugin("nr_dying_descendants"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "/"; args["debug"] = "true"; args["lte"] = "false"; // Greater than args["count"] = "29"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], ""), CgroupContext{.nr_dying_descendants = 30}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(NrDyingDescendants, MultiCgroupGt) { auto plugin = createPlugin("nr_dying_descendants"); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/cgroup"; args["cgroup"] = "above,above1,below"; args["debug"] = "true"; args["lte"] = "true"; args["count"] = "100"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 3); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "above"), CgroupContext{.nr_dying_descendants = 200}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "above1"), CgroupContext{.nr_dying_descendants = 300}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "below"), CgroupContext{.nr_dying_descendants = 90}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); } TEST(KillMemoryGrowth, KillsBigCgroup) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_memory_size_or_growth"; args["cgroup"] = "one_big/*"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{ .current_usage = 60, .average_usage = 60, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(123)); EXPECT_THAT(plugin->killed, Contains(456)); EXPECT_THAT(plugin->killed, Not(Contains(789))); EXPECT_THAT(plugin->killed, Not(Contains(111))); EXPECT_THAT( plugin->killed, Not(Contains(888))); // make sure there's no siblings } TEST(KillMemoryGrowth, KillsBigCgroupGrowth) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_memory_size_or_growth"; args["cgroup"] = "growth_big/*"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; // First test that we do the last ditch size killing. // // cgroup3 should be killed even though (30 / (21+20+30) < .5) ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "growth_big/cgroup1"), CgroupContext{ .current_usage = 21, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "growth_big/cgroup2"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "growth_big/cgroup3"), CgroupContext{ .current_usage = 30, .average_usage = 30, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(111)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); // Now lower average usage to artificially "boost" growth rate to trigger // growth kill ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "growth_big/cgroup1"), CgroupContext{ .current_usage = 21, .average_usage = 5, }); // Do the same thing for a sibling cgroup, but set the growth higher. This // tests that sibling removal occurs for growth kills too. ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{ .current_usage = 99, .average_usage = 5, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Not(Contains(888))); EXPECT_THAT(plugin->killed, Contains(123)); EXPECT_THAT(plugin->killed, Contains(456)); } TEST(KillMemoryGrowth, KillsBigCgroupMultiCgroup) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_memory_size_or_growth"; args["cgroup"] = "one_big/*,sibling/*"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{ .current_usage = 60, .average_usage = 60, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{ .current_usage = 100, .average_usage = 100, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(888)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(789))); EXPECT_THAT(plugin->killed, Not(Contains(111))); } TEST(KillMemoryGrowth, DoesntKillBigCgroupInDry) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_memory_size_or_growth"; args["cgroup"] = "one_big/*"; args["post_action_delay"] = "0"; args["dry"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{ .current_usage = 60, .average_usage = 60, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{ .current_usage = 20, .average_usage = 20, }); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_EQ(plugin->killed.size(), 0); } TEST(KillSwapUsage, KillsBigSwapCgroup) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_swap_usage"; args["cgroup"] = "one_big/*"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{.swap_usage = 20}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{.swap_usage = 60}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{.swap_usage = 40}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(789)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(111))); } TEST(KillSwapUsage, ThresholdTest) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_swap_usage"; args["meminfo_location"] = "oomd/fixtures/plugins/kill_by_swap_usage/meminfo"; args["cgroup"] = "one_big/*"; args["post_action_delay"] = "0"; args["threshold"] = "20%"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{.swap_usage = 1}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{.swap_usage = 2}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{.swap_usage = 3}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{.swap_usage = 20 << 10}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{.swap_usage = 60 << 10}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{.swap_usage = 40 << 10}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(789)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(111))); } TEST(KillSwapUsage, KillsBigSwapCgroupMultiCgroup) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_swap_usage"; args["cgroup"] = "one_big/*,sibling/*"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{.swap_usage = 20}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{.swap_usage = 60}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{.swap_usage = 40}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.swap_usage = 70}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(555)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(789))); EXPECT_THAT(plugin->killed, Not(Contains(111))); } TEST(KillSwapUsage, DoesntKillBigSwapCgroupDry) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_swap_usage"; args["cgroup"] = "one_big/*"; args["post_action_delay"] = "0"; args["dry"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{.swap_usage = 20}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{.swap_usage = 60}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{.swap_usage = 40}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_EQ(plugin->killed.size(), 0); } TEST(KillSwapUsage, DoesntKillNoSwap) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_swap_usage"; args["cgroup"] = "one_big/*"; args["post_action_delay"] = "0"; args["dry"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup1"), CgroupContext{}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup2"), CgroupContext{}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_big/cgroup3"), CgroupContext{}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::CONTINUE); EXPECT_EQ(plugin->killed.size(), 0); } TEST(KillPressure, KillsHighestPressure) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_pressure"; args["cgroup"] = "one_high/*"; args["resource"] = "io"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup1"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 60, .sec_60 = 60, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup2"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 50, .sec_60 = 70, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup3"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 80, .sec_60 = 80, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 99, .sec_60 = 99, .sec_600 = 99, }}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(111)); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(789))); EXPECT_THAT(plugin->killed, Not(Contains(888))); } TEST(KillPressure, KillsHighestPressureMultiCgroup) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_pressure"; args["cgroup"] = "one_high/*,sibling/*"; args["resource"] = "io"; args["post_action_delay"] = "0"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 2); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup1"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 60, .sec_60 = 60, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup2"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 50, .sec_60 = 70, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup3"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 80, .sec_60 = 80, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 99, .sec_60 = 99, .sec_600 = 99, }}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_THAT(plugin->killed, Contains(888)); EXPECT_THAT(plugin->killed, Not(Contains(111))); EXPECT_THAT(plugin->killed, Not(Contains(123))); EXPECT_THAT(plugin->killed, Not(Contains(456))); EXPECT_THAT(plugin->killed, Not(Contains(789))); } TEST(KillPressure, DoesntKillsHighestPressureDry) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["cgroup_fs"] = "oomd/fixtures/plugins/kill_by_pressure"; args["cgroup"] = "one_high/*"; args["resource"] = "io"; args["post_action_delay"] = "0"; args["dry"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); ASSERT_EQ(resources.size(), 1); OomdContext ctx; ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup1"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 60, .sec_60 = 60, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup2"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 50, .sec_60 = 70, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "one_high/cgroup3"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 80, .sec_60 = 80, }}); ctx.setCgroupContext( CgroupPath(args["cgroup_fs"], "sibling/cgroup1"), CgroupContext{.io_pressure = ResourcePressure{ .sec_10 = 99, .sec_60 = 99, .sec_600 = 99, }}); EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_EQ(plugin->killed.size(), 0); } oomd-0.3.1/src/oomd/plugins/DumpCgroupOverview.cpp000066400000000000000000000072101362162667600222160ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/DumpCgroupOverview.h" #include #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/include/CgroupPath.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace { auto constexpr kCgroupFs = "/sys/fs/cgroup"; auto constexpr kPgscanSwap = "pgscan_kswapd"; auto constexpr kPgscanDirect = "pgscan_direct"; void dumpCgroupOverview( const Oomd::CgroupPath& path, const Oomd::CgroupContext& cgroup_ctx, bool always) { // Only log on exceptional cases const auto& pressure = cgroup_ctx.pressure; bool should_dump = (always || (pressure.sec_10 >= 1 && pressure.sec_60 > 0)); if (!should_dump) { return; } const int64_t current = cgroup_ctx.current_usage; auto meminfo = Oomd::Fs::getMeminfo(); const int64_t swapfree = meminfo["SwapFree"]; const int64_t swaptotal = meminfo["SwapTotal"]; auto vmstat = Oomd::Fs::getVmstat(); const int64_t pgscan = vmstat[kPgscanSwap] + vmstat[kPgscanDirect]; std::ostringstream oss; oss << std::setprecision(2) << std::fixed; oss << "cgroup=" << path.relativePath() << " total=" << current / 1024 / 1024 << "MB pressure=" << pressure.sec_10 << ":" << pressure.sec_60 << ":" << pressure.sec_600 << " swapfree=" << swapfree / 1024 / 1024 << "MB/" << swaptotal / 1024 / 1024 << "MB pgscan=" << pgscan; OLOG << oss.str(); } } // namespace namespace Oomd { REGISTER_PLUGIN(dump_cgroup_overview, DumpCgroupOverview::create); int DumpCgroupOverview::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("always") != args.end()) { const auto& var = args.at("always"); if (var == "true" || var == "True" || var == "1") { always_ = true; } } return 0; } Engine::PluginRet DumpCgroupOverview::run(OomdContext& ctx) { std::unordered_set resolved_cgroups; for (const auto& cgroup : cgroups_) { auto resolved_raw_paths = Fs::resolveWildcardPath(cgroup); for (const auto& raw : resolved_raw_paths) { resolved_cgroups.emplace( cgroup.cgroupFs(), raw.substr(cgroup.cgroupFs().size())); } } for (const auto& resolved : resolved_cgroups) { try { const CgroupContext& cgroup_ctx = ctx.getCgroupContext(resolved); dumpCgroupOverview(resolved, cgroup_ctx, always_); } catch (const std::invalid_argument& ex) { // cgroup is ignored or omitted continue; } } return Engine::PluginRet::CONTINUE; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/DumpCgroupOverview.h000066400000000000000000000024131362162667600216630ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/engine/BasePlugin.h" #include #include namespace Oomd { class DumpCgroupOverview : public Engine::BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static DumpCgroupOverview* create() { return new DumpCgroupOverview(); } ~DumpCgroupOverview() = default; private: std::unordered_set cgroups_; bool always_{false}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/Exists.cpp000066400000000000000000000047111362162667600176640ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/Exists.h" #include #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup/"; namespace Oomd { REGISTER_PLUGIN(exists, Exists::create); int Exists::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("negate") != args.end()) { const std::string& val = args.at("negate"); if (val == "true" || val == "True" || val == "1") { negate_ = true; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } Engine::PluginRet Exists::run(OomdContext& ctx) { bool exists = false; for (const auto& cgroup : cgroups_) { for (const auto& path : ctx.cgroups()) { if (!::fnmatch( cgroup.absolutePath().c_str(), path.absolutePath().c_str(), 0)) { exists = true; goto out; } } } out: if (negate_) { exists = !exists; } if (exists) { return Engine::PluginRet::CONTINUE; } else { return Engine::PluginRet::STOP; } } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/Exists.h000066400000000000000000000023421362162667600173270ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class Exists : public Oomd::Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static Exists* create() { return new Exists(); } ~Exists() = default; private: std::unordered_set cgroups_; bool negate_{false}; bool debug_{false}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillIOCost-inl.h000066400000000000000000000065611362162667600206130ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/include/Types.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace Oomd { template int KillIOCost::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("post_action_delay") != args.end()) { int val = std::stoi(args.at("post_action_delay")); if (val < 0) { OLOG << "Argument=post_action_delay must be non-negative"; return 1; } post_action_delay_ = val; } if (args.find("dry") != args.end()) { const std::string& val = args.at("dry"); if (val == "true" || val == "True" || val == "1") { dry_ = true; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } template Engine::PluginRet KillIOCost::run(OomdContext& ctx) { bool ret = tryToKillSomething(ctx); if (ret) { std::this_thread::sleep_for(std::chrono::seconds(post_action_delay_)); return Engine::PluginRet::STOP; } else { return Engine::PluginRet::CONTINUE; } } template bool KillIOCost::tryToKillSomething(OomdContext& ctx) { auto io_cost_sorted = ctx.reverseSort( [](const CgroupContext& cgroup_ctx) { return cgroup_ctx.io_cost_rate; }); if (debug_) { OomdContext::dumpOomdContext(io_cost_sorted, !debug_); OLOG << "Removed sibling cgroups"; } OomdContext::removeSiblingCgroups(cgroups_, io_cost_sorted); OomdContext::dumpOomdContext(io_cost_sorted, !debug_); for (const auto& state_pair : io_cost_sorted) { OLOG << "Picked \"" << state_pair.first.relativePath() << "\" (" << state_pair.second.current_usage / 1024 / 1024 << "MB) based on io cost generation at " << state_pair.second.io_cost_rate; if (Base::tryToKillCgroup(state_pair.first.absolutePath(), true, dry_)) { Base::logKill( state_pair.first, state_pair.second, ctx.getActionContext(), dry_); return true; } } return false; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillIOCost.cpp000066400000000000000000000015721362162667600203630ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/KillIOCost.h" #include "oomd/PluginRegistry.h" namespace Oomd { REGISTER_PLUGIN(kill_by_io_cost, KillIOCost<>::create); } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillIOCost.h000066400000000000000000000027351362162667600200320ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/plugins/BaseKillPlugin.h" namespace Oomd { template class KillIOCost : public Base { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& ctx) override; static KillIOCost* create() { return new KillIOCost(); } ~KillIOCost() override = default; protected: virtual bool tryToKillSomething(OomdContext& ctx); std::unordered_set cgroups_; int post_action_delay_{15}; bool dry_{false}; bool debug_{false}; private: static auto constexpr kCgroupFs = "/sys/fs/cgroup"; }; } // namespace Oomd #include "oomd/plugins/KillIOCost-inl.h" oomd-0.3.1/src/oomd/plugins/KillMemoryGrowth-inl.h000066400000000000000000000157361362162667600221220ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/include/Types.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace { auto constexpr kCgroupFs = "/sys/fs/cgroup"; } // namespace namespace Oomd { template int KillMemoryGrowth::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("size_threshold") != args.end()) { int val = std::stoi(args.at("size_threshold")); if (val < 0) { OLOG << "Argument=size_threshold must be non-negative"; return 1; } size_threshold_ = val; } if (args.find("growing_size_percentile") != args.end()) { int val = std::stoi(args.at("growing_size_percentile")); if (val < 0) { OLOG << "Argument=growing_size_percentile must be non-negative"; return 1; } growing_size_percentile_ = val; } if (args.find("min_growth_ratio") != args.end()) { int val = std::stof(args.at("min_growth_ratio")); if (val < 0) { OLOG << "Argument=min_growth_ratio must be non-negative"; return 1; } min_growth_ratio_ = val; } if (args.find("post_action_delay") != args.end()) { int val = std::stoi(args.at("post_action_delay")); if (val < 0) { OLOG << "Argument=post_action_delay must be non-negative"; return 1; } post_action_delay_ = val; } if (args.find("dry") != args.end()) { const std::string& val = args.at("dry"); if (val == "true" || val == "True" || val == "1") { dry_ = true; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } template Engine::PluginRet KillMemoryGrowth::run(OomdContext& ctx) { // First try to kill by size, respecting size_threshold. Failing that, // try to kill by growth. Failing that, try to kill by size, ignoring // size_threshold. bool ret = tryToKillBySize(ctx, false); if (!ret) { ret = tryToKillByGrowth(ctx); } if (!ret) { ret = tryToKillBySize(ctx, true); } if (ret) { std::this_thread::sleep_for(std::chrono::seconds(post_action_delay_)); return Engine::PluginRet::STOP; } else { return Engine::PluginRet::CONTINUE; } } template bool KillMemoryGrowth::tryToKillBySize( OomdContext& ctx, bool ignore_threshold) { // Sort all the cgroups by (size - memory.low) and remove all the cgroups // we are not assigned to kill auto size_sorted = ctx.reverseSort([](const CgroupContext& cgroup_ctx) { return cgroup_ctx.effective_usage(); }); OomdContext::removeSiblingCgroups(cgroups_, size_sorted); OomdContext::dumpOomdContext(size_sorted, !debug_); int64_t cur_memcurrent = 0; for (const auto& state_pair : size_sorted) { cur_memcurrent += state_pair.second.current_usage; } // First try to kill the biggest cgroup over it's assigned memory.low for (const auto& state_pair : size_sorted) { if (!ignore_threshold && state_pair.second.current_usage < (cur_memcurrent * (static_cast(size_threshold_) / 100))) { OLOG << "Skipping size heuristic kill on " << state_pair.first.relativePath() << " b/c not big enough"; break; } OLOG << "Picked \"" << state_pair.first.relativePath() << "\" (" << state_pair.second.current_usage / 1024 / 1024 << "MB) based on size > " << size_threshold_ << "% of total " << cur_memcurrent / 1024 / 1024 << "MB" << (ignore_threshold ? " (size threshold overridden)" : ""); if (Base::tryToKillCgroup(state_pair.first.absolutePath(), true, dry_)) { Base::logKill( state_pair.first, state_pair.second, ctx.getActionContext(), dry_); return true; } } return false; } template bool KillMemoryGrowth::tryToKillByGrowth(OomdContext& ctx) { // Pick the top P(growing_size_percentile_) and sort them by the growth rate // (current usage / avg usage) and try to kill the highest one. auto growth_sorted = ctx.reverseSort([](const CgroupContext& cgroup_ctx) { return cgroup_ctx.effective_usage(); }); OomdContext::removeSiblingCgroups(cgroups_, growth_sorted); const size_t nr = std::ceil( growth_sorted.size() * (100 - static_cast(growing_size_percentile_)) / 100); while (growth_sorted.size() > nr) { growth_sorted.pop_back(); } OomdContext::reverseSort(growth_sorted, [](const CgroupContext& cgroup_ctx) { return static_cast(cgroup_ctx.current_usage) / cgroup_ctx.average_usage; }); OomdContext::dumpOomdContext(growth_sorted, !debug_); for (const auto& state_pair : growth_sorted) { float growth_ratio = static_cast(state_pair.second.current_usage) / state_pair.second.average_usage; if (growth_ratio < min_growth_ratio_) { OLOG << "Skipping growth heuristic kill on " << state_pair.first.relativePath() << " b/c growth is less than " << min_growth_ratio_; break; } std::ostringstream oss; oss << std::setprecision(2) << std::fixed; oss << "Picked \"" << state_pair.first.relativePath() << "\" (" << state_pair.second.current_usage / 1024 / 1024 << "MB) based on growth rate " << static_cast(state_pair.second.current_usage) / state_pair.second.average_usage << " among P" << growing_size_percentile_ << " largest"; OLOG << oss.str(); if (Base::tryToKillCgroup(state_pair.first.absolutePath(), true, dry_)) { Base::logKill( state_pair.first, state_pair.second, ctx.getActionContext(), dry_); return true; } } return false; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillMemoryGrowth.cpp000066400000000000000000000015621362162667600216650ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/KillMemoryGrowth.h" namespace Oomd { REGISTER_PLUGIN(kill_by_memory_size_or_growth, KillMemoryGrowth<>::create); } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillMemoryGrowth.h000066400000000000000000000031311362162667600213240ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/plugins/BaseKillPlugin.h" namespace Oomd { template class KillMemoryGrowth : public Base { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& ctx) override; static KillMemoryGrowth* create() { return new KillMemoryGrowth(); } ~KillMemoryGrowth() = default; protected: virtual bool tryToKillBySize(OomdContext& ctx, bool ignore_threshold); virtual bool tryToKillByGrowth(OomdContext& ctx); std::unordered_set cgroups_; int size_threshold_{50}; int growing_size_percentile_{80}; float min_growth_ratio_{1.25}; int post_action_delay_{15}; bool dry_{false}; bool debug_{false}; }; } // namespace Oomd #include "oomd/plugins/KillMemoryGrowth-inl.h" oomd-0.3.1/src/oomd/plugins/KillPressure-inl.h000066400000000000000000000110531362162667600212530ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/include/Types.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace Oomd { template int KillPressure::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("resource") != args.end() && (args.at("resource") == "io" || args.at("resource") == "memory")) { const auto& res = args.at("resource"); if (res == "io") { resource_ = ResourceType::IO; } else if (res == "memory") { resource_ = ResourceType::MEMORY; } } else { OLOG << "Argument=resource missing or not (io|memory)"; return 1; } if (args.find("post_action_delay") != args.end()) { int val = std::stoi(args.at("post_action_delay")); if (val < 0) { OLOG << "Argument=post_action_delay must be non-negative"; return 1; } post_action_delay_ = val; } if (args.find("dry") != args.end()) { const std::string& val = args.at("dry"); if (val == "true" || val == "True" || val == "1") { dry_ = true; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } template Engine::PluginRet KillPressure::run(OomdContext& ctx) { bool ret = tryToKillSomething(ctx); if (ret) { std::this_thread::sleep_for(std::chrono::seconds(post_action_delay_)); return Engine::PluginRet::STOP; } else { return Engine::PluginRet::CONTINUE; } } template bool KillPressure::tryToKillSomething(OomdContext& ctx) { auto pressure_sorted = ctx.reverseSort([this](const CgroupContext& cgroup_ctx) { int average = 0; switch (resource_) { case ResourceType::IO: average = cgroup_ctx.io_pressure.sec_10 / 2 + cgroup_ctx.io_pressure.sec_60 / 2; break; case ResourceType::MEMORY: average = cgroup_ctx.pressure.sec_10 / 2 + cgroup_ctx.pressure.sec_60 / 2; break; } return average; }); if (debug_) { OomdContext::dumpOomdContext(pressure_sorted, !debug_); OLOG << "Removed sibling cgroups"; } OomdContext::removeSiblingCgroups(cgroups_, pressure_sorted); OomdContext::dumpOomdContext(pressure_sorted, !debug_); for (const auto& state_pair : pressure_sorted) { float pressure10 = 0; float pressure60 = 0; switch (resource_) { case ResourceType::IO: pressure10 = state_pair.second.io_pressure.sec_10; pressure60 = state_pair.second.io_pressure.sec_60; break; case ResourceType::MEMORY: pressure10 = state_pair.second.pressure.sec_10; pressure60 = state_pair.second.pressure.sec_60; break; } OLOG << "Picked \"" << state_pair.first.relativePath() << "\" (" << state_pair.second.current_usage / 1024 / 1024 << "MB) based on pressure generation at " << "10s=" << pressure10 << " 60s=" << pressure60; if (Base::tryToKillCgroup(state_pair.first.absolutePath(), true, dry_)) { Base::logKill( state_pair.first, state_pair.second, ctx.getActionContext(), dry_); return true; } } return false; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillPressure.cpp000066400000000000000000000015771362162667600210400ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/KillPressure.h" #include "oomd/PluginRegistry.h" namespace Oomd { REGISTER_PLUGIN(kill_by_pressure, KillPressure<>::create); } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillPressure.h000066400000000000000000000027701362162667600205010ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/plugins/BaseKillPlugin.h" namespace Oomd { template class KillPressure : public Base { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& ctx) override; static KillPressure* create() { return new KillPressure(); } ~KillPressure() = default; protected: virtual bool tryToKillSomething(OomdContext& ctx); std::unordered_set cgroups_; ResourceType resource_; int post_action_delay_{15}; bool dry_{false}; bool debug_{false}; private: static auto constexpr kCgroupFs = "/sys/fs/cgroup"; }; } // namespace Oomd #include "oomd/plugins/KillPressure-inl.h" oomd-0.3.1/src/oomd/plugins/KillSwapUsage-inl.h000066400000000000000000000075161362162667600213530ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "oomd/Log.h" #include "oomd/include/Types.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace Oomd { template int KillSwapUsage::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("threshold") != args.end()) { auto meminfo = args.find("meminfo_location") != args.end() ? Fs::getMeminfo(args.at("meminfo_location")) : Fs::getMeminfo(); if (Util::parseSizeOrPercent( args.at("threshold"), &threshold_, meminfo.at("SwapTotal")) != 0) { OLOG << "Failed to parse threshold" << args.at("threshold"); return 1; } } if (args.find("post_action_delay") != args.end()) { int val = std::stoi(args.at("post_action_delay")); if (val < 0) { OLOG << "Argument=post_action_delay must be non-negative"; return 1; } post_action_delay_ = val; } if (args.find("dry") != args.end()) { const std::string& val = args.at("dry"); if (val == "true" || val == "True" || val == "1") { dry_ = true; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } template Engine::PluginRet KillSwapUsage::run(OomdContext& ctx) { bool ret = tryToKillSomething(ctx); if (ret) { std::this_thread::sleep_for(std::chrono::seconds(post_action_delay_)); return Engine::PluginRet::STOP; } else { return Engine::PluginRet::CONTINUE; } } template bool KillSwapUsage::tryToKillSomething(OomdContext& ctx) { auto swap_sorted = ctx.reverseSort( [](const CgroupContext& cgroup_ctx) { return cgroup_ctx.swap_usage; }); if (debug_) { OomdContext::dumpOomdContext(swap_sorted, !debug_); OLOG << "Removed sibling cgroups"; } OomdContext::removeSiblingCgroups(cgroups_, swap_sorted); OomdContext::dumpOomdContext(swap_sorted, !debug_); for (const auto& state_pair : swap_sorted) { if (state_pair.second.swap_usage < threshold_) { break; } OLOG << "Picked \"" << state_pair.first.relativePath() << "\" (" << state_pair.second.current_usage / 1024 / 1024 << "MB) based on swap usage at " << state_pair.second.swap_usage / 1024 / 1024 << "MB"; if (Base::tryToKillCgroup(state_pair.first.absolutePath(), true, dry_)) { Base::logKill( state_pair.first, state_pair.second, ctx.getActionContext(), dry_); return true; } } return false; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillSwapUsage.cpp000066400000000000000000000016031362162667600211150ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/KillSwapUsage.h" #include "oomd/PluginRegistry.h" namespace Oomd { REGISTER_PLUGIN(kill_by_swap_usage, KillSwapUsage<>::create); } // namespace Oomd oomd-0.3.1/src/oomd/plugins/KillSwapUsage.h000066400000000000000000000030501362162667600205600ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include "oomd/plugins/BaseKillPlugin.h" namespace Oomd { template class KillSwapUsage : public Base { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& ctx) override; static KillSwapUsage* create() { return new KillSwapUsage(); } ~KillSwapUsage() = default; protected: virtual bool tryToKillSomething(OomdContext& ctx); std::unordered_set cgroups_; // Default threshold is to kill something with non-zero swap usage int64_t threshold_{1}; int post_action_delay_{15}; bool dry_{false}; bool debug_{false}; private: static constexpr auto kCgroupFs = "/sys/fs/cgroup"; }; } // namespace Oomd #include "oomd/plugins/KillSwapUsage-inl.h" oomd-0.3.1/src/oomd/plugins/MemoryAbove.cpp000066400000000000000000000116341362162667600206340ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/MemoryAbove.h" #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup/"; namespace Oomd { REGISTER_PLUGIN(memory_above, MemoryAbove::create); int MemoryAbove::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } auto meminfo = args.find("meminfo_location") != args.end() ? Fs::getMeminfo(args.at("meminfo_location")) : Fs::getMeminfo(); if (args.find("threshold_anon") != args.end()) { if (Util::parseSizeOrPercent( args.at("threshold_anon"), &threshold_, meminfo.at("MemTotal")) != 0) { OLOG << "Failed to parse threshold_anon=" << args.at("threshold_anon"); return 1; } is_anon_ = true; } else if (args.find("threshold") != args.end()) { if (Util::parseSizeOrPercent( args.at("threshold"), &threshold_, meminfo.at("MemTotal")) < 0) { OLOG << "Failed to parse threshold=" << args.at("threshold"); return 1; } } else { OLOG << "Argument=[anon_]threshold not present"; return 1; } if (args.find("duration") != args.end()) { duration_ = std::stoi(args.at("duration")); } else { OLOG << "Argument=duration not present"; return 1; } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } Engine::PluginRet MemoryAbove::run(OomdContext& ctx) { using std::chrono::steady_clock; int64_t current_memory_usage = 0; std::string current_cgroup; for (const auto& cgroup : cgroups_) { try { auto cgroup_ctx = ctx.getCgroupContext(cgroup); if (debug_) { OLOG << "cgroup \"" << cgroup.relativePath() << "\" " << "memory.current=" << cgroup_ctx.current_usage << "memory.stat (anon)=" << cgroup_ctx.anon_usage; } auto usage = is_anon_ ? cgroup_ctx.anon_usage : cgroup_ctx.current_usage; if (current_memory_usage < usage) { current_memory_usage = usage; current_cgroup = cgroup.relativePath(); } } catch (const std::exception& ex) { OLOG << "Failed to get cgroup \"" << cgroup.relativePath() << "\" " << "context: " << ex.what(); continue; } } const auto now = steady_clock::now(); if (current_memory_usage > threshold_) { // Logging this on every positive match is too verbose. Daniel is // fixing it properly but let's shut it up for the time being. if (debug_) { OLOG << "cgroup \"" << current_cgroup << "\" " << (is_anon_ ? "anon usage=" : "memory usage=") << current_memory_usage << " hit threshold=" << threshold_; } if (hit_thres_at_ == steady_clock::time_point()) { hit_thres_at_ = now; } const auto diff = std::chrono::duration_cast(now - hit_thres_at_) .count(); if (diff >= duration_) { // Logging this on every positive match is too verbose. Daniel is // fixing it properly but let's shut it up for the time being. if (debug_) { std::ostringstream oss; oss << std::setprecision(2) << std::fixed; oss << "cgroup \"" << current_cgroup << "\" " << "current memory usage " << current_memory_usage / 1024 / 1024 << "MB is over the threshold of " << threshold_ / 1024 / 1024 << "MB for " << duration_ << " seconds"; OLOG << oss.str(); } return Engine::PluginRet::CONTINUE; } } else { hit_thres_at_ = steady_clock::time_point(); } return Engine::PluginRet::STOP; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/MemoryAbove.h000066400000000000000000000026621362162667600203020ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class MemoryAbove : public Oomd::Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static MemoryAbove* create() { return new MemoryAbove(); } ~MemoryAbove() = default; private: std::unordered_set cgroups_; // Initialized to bogus values; init() will crash oomd if non-0 return int64_t threshold_; int duration_; bool is_anon_{false}; bool debug_{false}; std::chrono::steady_clock::time_point hit_thres_at_{}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/MemoryReclaim.cpp000066400000000000000000000050631362162667600211530ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/MemoryReclaim.h" #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup/"; static constexpr auto kPgscan = "pgscan"; namespace Oomd { REGISTER_PLUGIN(memory_reclaim, MemoryReclaim::create); int MemoryReclaim::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("duration") != args.end()) { duration_ = std::stoi(args.at("duration")); } else { OLOG << "Argument=duration not present"; return 1; } // Success return 0; } Engine::PluginRet MemoryReclaim::run(OomdContext& ctx) { using std::chrono::steady_clock; auto resolved_cgroups = ctx.reverseSort(); OomdContext::removeSiblingCgroups(cgroups_, resolved_cgroups); int64_t pgscan = 0; for (const auto& cg : resolved_cgroups) { auto memstat = Fs::getMemstat(cg.first.absolutePath()); pgscan += memstat[kPgscan]; } OOMD_SCOPE_EXIT { last_pgscan_ = pgscan; }; const auto now = steady_clock::now(); if (pgscan > last_pgscan_) { last_reclaim_at_ = now; } const auto diff = std::chrono::duration_cast(now - last_reclaim_at_) .count(); if (diff <= duration_) { return Engine::PluginRet::CONTINUE; } else { return Engine::PluginRet::STOP; } } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/MemoryReclaim.h000066400000000000000000000025111362162667600206130ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class MemoryReclaim : public Engine::BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static MemoryReclaim* create() { return new MemoryReclaim(); } ~MemoryReclaim() = default; private: std::unordered_set cgroups_; int duration_; int64_t last_pgscan_{0}; std::chrono::steady_clock::time_point last_reclaim_at_{}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/NrDyingDescendants.cpp000066400000000000000000000053751362162667600221420ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/NrDyingDescendants.h" #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup/"; namespace Oomd { REGISTER_PLUGIN(nr_dying_descendants, NrDyingDescendants::create); int NrDyingDescendants::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("count") != args.end()) { int val = std::stoi(args.at("count")); if (val < 0) { OLOG << "Argument=count must be non-negative"; } count_ = val; } else { OLOG << "Argument=count not present"; } if (args.find("lte") != args.end()) { const std::string& val = args.at("lte"); if (val == "true" || val == "True" || val == "1") { lte_ = true; } if (val == "false" || val == "False" || val == "0") { lte_ = false; } } if (args.find("debug") != args.end()) { const std::string& val = args.at("debug"); if (val == "true" || val == "True" || val == "1") { debug_ = true; } } // Success return 0; } Engine::PluginRet NrDyingDescendants::run(OomdContext& ctx) { auto cgroups = ctx.reverseSort(); OomdContext::removeSiblingCgroups(cgroups_, cgroups); for (const auto& [name, cgroup_ctx] : cgroups) { int64_t nr = cgroup_ctx.nr_dying_descendants; if ((lte_ && nr <= count_) || (!lte_ && nr > count_)) { if (debug_) { OLOG << "nr_dying_descendants=" << nr << (lte_ ? " <= " : " > ") << "count=" << count_; } return Engine::PluginRet::CONTINUE; } } return Engine::PluginRet::STOP; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/NrDyingDescendants.h000066400000000000000000000024711362162667600216010ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class NrDyingDescendants : public Oomd::Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& ctx) override; static NrDyingDescendants* create() { return new NrDyingDescendants(); } ~NrDyingDescendants() override = default; private: std::unordered_set cgroups_; int64_t count_{0}; bool lte_{true}; // less than or equal bool debug_{false}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/PressureAbove.cpp000066400000000000000000000103251362162667600211700ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/PressureAbove.h" #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup/"; namespace Oomd { REGISTER_PLUGIN(pressure_above, PressureAbove::create); int PressureAbove::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("resource") != args.end() && (args.at("resource") == "io" || args.at("resource") == "memory")) { const auto& res = args.at("resource"); if (res == "io") { resource_ = ResourceType::IO; } else if (res == "memory") { resource_ = ResourceType::MEMORY; } } else { OLOG << "Argument=resource missing or not (io|memory)"; return 1; } if (args.find("threshold") != args.end()) { threshold_ = std::stoi(args.at("threshold")); } else { OLOG << "Argument=threshold not present"; return 1; } if (args.find("duration") != args.end()) { duration_ = std::stoi(args.at("duration")); } else { OLOG << "Argument=duration not present"; return 1; } // Success return 0; } Engine::PluginRet PressureAbove::run(OomdContext& ctx) { using std::chrono::steady_clock; ResourcePressure current_pressure; int64_t current_memory_usage = 0; auto sorted_cgroups = ctx.reverseSort(); OomdContext::removeSiblingCgroups(cgroups_, sorted_cgroups); for (const auto& state_pair : sorted_cgroups) { ResourcePressure rp; switch (resource_) { case ResourceType::IO: rp = state_pair.second.io_pressure; break; case ResourceType::MEMORY: rp = state_pair.second.pressure; break; // No default to catch new additions in ResourceType } // Do a weighted comparison (we care more about 10s, then 60s, then 600s) if (rp.sec_10 * 3 + rp.sec_60 * 2 + rp.sec_600 > current_pressure.sec_10 * 3 + current_pressure.sec_60 * 2 + current_pressure.sec_600) { current_pressure = rp; current_memory_usage = state_pair.second.current_usage; } } OOMD_SCOPE_EXIT { last_pressure_ = current_pressure; }; const auto now = steady_clock::now(); // Check if the 10s pressure is above threshold_ for duration_ if (current_pressure.sec_10 > threshold_) { if (hit_thres_at_ == steady_clock::time_point()) { hit_thres_at_ = now; } const auto diff = std::chrono::duration_cast(now - hit_thres_at_) .count(); if (diff >= duration_) { std::ostringstream oss; oss << std::setprecision(2) << std::fixed; oss << "10s pressure " << current_pressure.sec_10 << " is over the threshold of " << threshold_ << " for " << duration_ << " seconds , total usage is " << current_memory_usage / 1024 / 1024 << "MB"; OLOG << oss.str(); return Engine::PluginRet::CONTINUE; } } else { hit_thres_at_ = steady_clock::time_point(); } return Engine::PluginRet::STOP; } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/PressureAbove.h000066400000000000000000000027251362162667600206420ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class PressureAbove : public Oomd::Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static PressureAbove* create() { return new PressureAbove(); } ~PressureAbove() = default; private: std::unordered_set cgroups_; ResourceType resource_; // Initialized to bogus values; init() will crash oomd if non-0 return int threshold_; int duration_; ResourcePressure last_pressure_{100, 100, 100}; std::chrono::steady_clock::time_point hit_thres_at_{}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/PressureRisingBeyond.cpp000066400000000000000000000114331362162667600225310ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/PressureRisingBeyond.h" #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/util/Fs.h" #include "oomd/util/ScopeGuard.h" #include "oomd/util/Util.h" static constexpr auto kCgroupFs = "/sys/fs/cgroup"; namespace Oomd { REGISTER_PLUGIN(pressure_rising_beyond, PressureRisingBeyond::create); int PressureRisingBeyond::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { resources.emplace(cgroup_fs, c); cgroups_.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("resource") != args.end() && (args.at("resource") == "io" || args.at("resource") == "memory")) { const auto& res = args.at("resource"); if (res == "io") { resource_ = ResourceType::IO; } else if (res == "memory") { resource_ = ResourceType::MEMORY; } } else { OLOG << "Argument=resource missing or not (io|memory)"; return 1; } if (args.find("threshold") != args.end()) { threshold_ = std::stoi(args.at("threshold")); } else { OLOG << "Argument=threshold not present"; return 1; } if (args.find("duration") != args.end()) { duration_ = std::stoi(args.at("duration")); } else { OLOG << "Argument=duration not present"; return 1; } // `fast_fall_ratio` is optional if (args.find("fast_fall_ratio") != args.end()) { fast_fall_ratio_ = std::stof(args.at("fast_fall_ratio")); } else { fast_fall_ratio_ = 0.85; } // Success return 0; } Engine::PluginRet PressureRisingBeyond::run(OomdContext& ctx) { using std::chrono::steady_clock; ResourcePressure current_pressure; int64_t current_memory_usage = 0; auto sorted_cgroups = ctx.reverseSort(); OomdContext::removeSiblingCgroups(cgroups_, sorted_cgroups); for (const auto& state_pair : sorted_cgroups) { ResourcePressure rp; switch (resource_) { case ResourceType::IO: rp = state_pair.second.io_pressure; break; case ResourceType::MEMORY: rp = state_pair.second.pressure; break; // No default case to catch for future additions to ResourceType } // Do a weighted comparison (we care more about 10s, then 60s, then 600s) if (rp.sec_10 * 3 + rp.sec_60 * 2 + rp.sec_600 > current_pressure.sec_10 * 3 + current_pressure.sec_60 * 2 + current_pressure.sec_600) { current_pressure = rp; current_memory_usage = state_pair.second.current_usage; } } OOMD_SCOPE_EXIT { last_pressure_ = current_pressure; }; const auto now = steady_clock::now(); // Check if the 60s pressure is above threshold_ for duration_ bool pressure_duration_met_60s = false; if (current_pressure.sec_60 > threshold_) { if (hit_thres_at_ == steady_clock::time_point()) { hit_thres_at_ = now; } const auto diff = std::chrono::duration_cast(now - hit_thres_at_) .count(); if (diff >= duration_) { pressure_duration_met_60s = true; } } else { hit_thres_at_ = steady_clock::time_point(); } bool above_threshold_10s = current_pressure.sec_10 > threshold_; bool falling_rapidly_10s = current_pressure.sec_10 < last_pressure_.sec_10 * fast_fall_ratio_; if (pressure_duration_met_60s && above_threshold_10s && !falling_rapidly_10s) { std::ostringstream oss; oss << std::setprecision(2) << std::fixed; oss << "1m pressure " << current_pressure.sec_60 << " is over the threshold of " << threshold_ << " for " << duration_ << " seconds , total usage is " << current_memory_usage / 1024 / 1024 << "MB"; OLOG << oss.str(); return Engine::PluginRet::CONTINUE; } else { return Engine::PluginRet::STOP; } } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/PressureRisingBeyond.h000066400000000000000000000030131362162667600221710ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class PressureRisingBeyond : public Oomd::Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static PressureRisingBeyond* create() { return new PressureRisingBeyond(); } ~PressureRisingBeyond() = default; private: std::unordered_set cgroups_; ResourceType resource_; // Initialized to bogus values; init() will crash oomd if non-0 return int threshold_; int duration_; float fast_fall_ratio_; ResourcePressure last_pressure_{100, 100, 100}; std::chrono::steady_clock::time_point hit_thres_at_{}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/Senpai.cpp000066400000000000000000000210001362162667600176120ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/Senpai.h" #include #include #include #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/Stats.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" namespace { auto constexpr kCgroupFs = "/sys/fs/cgroup"; } // namespace namespace Oomd { REGISTER_PLUGIN(senpai, Senpai::create); int Senpai::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { { struct stat s; has_memory_high_tmp_ = !stat("/sys/fs/cgroup/system.slice/memory.high.tmp", &s); } if (args.find("cgroup") != args.end()) { auto cgroup_fs = (args.find("cgroup_fs") != args.end() ? args.at("cgroup_fs") : kCgroupFs); auto cgroups = Util::split(args.at("cgroup"), ','); for (const auto& c : cgroups) { cgroups_.emplace(cgroup_fs, c); resources.emplace(cgroup_fs, c); } } else { OLOG << "Argument=cgroup not present"; return 1; } if (args.find("limit_min_bytes") != args.end()) { limit_min_bytes_ = std::stoull(args.at("limit_min_bytes")); } if (args.find("limit_max_bytes") != args.end()) { limit_max_bytes_ = std::stoull(args.at("limit_max_bytes")); } if (args.find("interval") != args.end()) { interval_ = std::stoull(args.at("interval")); } if (args.find("pressure_ms") != args.end()) { pressure_ms_ = std::chrono::milliseconds(std::stoull(args.at("pressure_ms"))); } if (args.find("max_probe") != args.end()) { max_probe_ = std::stod(args.at("max_probe")); } if (args.find("max_backoff") != args.end()) { max_backoff_ = std::stod(args.at("max_backoff")); } if (args.find("coeff_probe") != args.end()) { coeff_probe_ = std::stod(args.at("coeff_probe")); } if (args.find("coeff_backoff") != args.end()) { coeff_backoff_ = std::stod(args.at("coeff_backoff")); } return 0; } Engine::PluginRet Senpai::run(OomdContext& ctx) { std::set resolved_cgroups; for (const auto& cgroup : cgroups_) { auto resolved = Fs::resolveWildcardPath(cgroup); for (auto&& cg : std::move(resolved)) { if (Fs::isDir(cg)) { resolved_cgroups.emplace(cg); } } } auto new_cgroups = addRemoveTrackedCgroups(resolved_cgroups); for (auto& cg : tracked_cgroups_) { tick(cg.first, cg.second); } // new cgroups will be polled after a "tick" has elapsed, so add // them to the tracked group at the end here tracked_cgroups_.merge(std::move(new_cgroups)); return Engine::PluginRet::CONTINUE; } Senpai::CgroupState::CgroupState( uint64_t start_limit, std::chrono::microseconds total, uint64_t start_ticks, const std::string& path) : limit{start_limit}, last_total{total}, ticks{start_ticks} {} namespace { std::chrono::microseconds getTotal(const std::string& name) { // Senpai reads pressure.some to get early notice that a workload // may be under resource pressure const auto pressure = Oomd::Fs::readMempressure(name, Oomd::Fs::PressureType::SOME); if (!pressure.total) { throw std::runtime_error("Senpai enabled but no total pressure info"); } return pressure.total.value(); } uint64_t getCurrent(const std::string& name) { return static_cast(Oomd::Fs::readMemcurrent(name)); } } // namespace std::map Senpai::addRemoveTrackedCgroups( const std::set& resolved_cgroups) { std::map new_cgroups; auto resolvedIt = resolved_cgroups.cbegin(); auto trackedIt = tracked_cgroups_.begin(); while (resolvedIt != resolved_cgroups.cend()) { if (trackedIt == tracked_cgroups_.end()) { // The rest of the resolved cgroups are not tracked, track them for (auto it = resolvedIt; it != resolved_cgroups.cend(); ++it) { auto state = initializeCgroup(*it); new_cgroups.emplace(std::move(*it), std::move(state)); } return new_cgroups; } if (*resolvedIt < trackedIt->first) { // Resolved cgroup not in tracked map, track it auto state = initializeCgroup(*resolvedIt); new_cgroups.emplace(std::move(*resolvedIt), std::move(state)); ++resolvedIt; } else { if (trackedIt->first < *resolvedIt) { // tracked cgroup not found, erase it trackedIt = tracked_cgroups_.erase(trackedIt); } else { ++resolvedIt; ++trackedIt; } } } tracked_cgroups_.erase(trackedIt, tracked_cgroups_.end()); return new_cgroups; } void Senpai::tick(const std::string& name, CgroupState& state) { auto limit = static_cast(readMemhigh(name)); auto total = getTotal(name); auto factor = 0.0; if (limit != state.limit) { // Something else changed limits on this cgroup or it was // recreated in-between ticks - reset the state and return, // unfortuantely, the rest of this logic is still racy after this // point std::ostringstream oss; oss << "cgroup " << name << " memory.high " << limit << " does not match recorded state " << state.limit << ". Resetting cgroup"; OLOG << oss.str(); state = initializeCgroup(name); return; } // Adjust cgroup limit by factor auto adjust = [&](double factor) { state.limit += state.limit * factor; state.limit = std::max(limit_min_bytes_, std::min(limit_max_bytes_, state.limit)); // Memory high is always a multiple of 4K state.limit &= ~0xFFF; writeMemhigh(name, state.limit); state.ticks = interval_; state.cumulative = std::chrono::microseconds{0}; }; auto delta = total - state.last_total; state.last_total = total; state.cumulative += delta; auto cumulative = state.cumulative.count(); if (state.cumulative >= pressure_ms_) { // Excessive pressure, back off. The rate scales exponentially // with pressure deviation. The coefficient defines how sensitive // we are to fluctuations around the target pressure: when the // coefficient is 10, the adjustment curve reaches the backoff // limit when observed pressure is ten times the target pressure. double error = state.cumulative / pressure_ms_; factor = error / coeff_backoff_; factor *= factor; factor = std::min(factor * max_backoff_, max_backoff_); adjust(factor); } else if (state.ticks) { --state.ticks; } else { // Pressure too low, tighten the limit. Like when backing off, the // adjustment becomes exponentially more aggressive as observed // pressure falls below the target pressure. The adjustment limit // is reached when stall time falls through pressure/coeff_probe_. auto one = std::chrono::microseconds{1}; double error = pressure_ms_ / std::max(state.cumulative, one); factor = error / coeff_probe_; factor *= factor; factor = std::min(factor * max_probe_, max_probe_); factor = -factor; adjust(factor); } std::ostringstream oss; oss << "cgroup " << name << std::setprecision(3) << std::fixed << " limitgb " << limit / (double)(1 << 30UL) << " totalus " << total.count() << " deltaus " << delta.count() << " cumus " << cumulative << " ticks " << state.ticks << std::defaultfloat << " adjust " << factor; OLOG << oss.str(); } Senpai::CgroupState Senpai::initializeCgroup(const std::string& path) { auto start_limit = getCurrent(path); writeMemhigh(path, start_limit); return CgroupState(start_limit, getTotal(path), interval_, path); } int64_t Senpai::readMemhigh(const std::string& path) { if (has_memory_high_tmp_) { return Oomd::Fs::readMemhightmp(path); } else { return Oomd::Fs::readMemhigh(path); } } void Senpai::writeMemhigh(const std::string& path, int64_t value) { if (has_memory_high_tmp_) { Oomd::Fs::writeMemhightmp(path, value, std::chrono::seconds(20)); } else { Oomd::Fs::writeMemhigh(path, value); } } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/Senpai.h000066400000000000000000000060301362162667600172650ustar00rootroot00000000000000/* * Copyright (C) 2019-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/engine/BasePlugin.h" #include #include #include #include #include namespace Oomd { /* * A plugin which adjusts memory.high on a cgroup in order to create a * light amount of memory pressure. This allows memory.current to more * accurately represent the amount of memory required by the cgroup. */ class Senpai : public Engine::BasePlugin { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& ctx) override; static Senpai* create() { return new Senpai(); } ~Senpai() = default; private: struct CgroupState { CgroupState( uint64_t start_limit, std::chrono::microseconds total, uint64_t start_ticks, const std::string& path); // Current memory limit uint64_t limit; // Last recorded total memory pressure std::chrono::microseconds last_total; // Cumulative memory pressure since last adjustment std::chrono::microseconds cumulative{0}; // Count-down to decision to probe/backoff uint64_t ticks; }; // Removes any untracked cgroups and returns new cgroups to be watched std::map addRemoveTrackedCgroups( const std::set& resolved_cgroups); void tick(const std::string& name, CgroupState& state); CgroupState initializeCgroup(const std::string& path); // Uses memory.high.tmp if available int64_t readMemhigh(const std::string& path); void writeMemhigh(const std::string& path, int64_t value); std::unordered_set cgroups_; std::map tracked_cgroups_; bool has_memory_high_tmp_{false}; // cgroup size limits uint64_t limit_min_bytes_{1ull << 30}; uint64_t limit_max_bytes_{500ull << 30}; // pressure target - stall time over sampling period uint64_t interval_{6}; std::chrono::microseconds pressure_ms_{std::chrono::milliseconds{10}}; // translate observed target deviation to cgroup adjustment rate // - max_probe is reached when stalling falls below pressure / coeff_probe // - max_backoff is reached when stalling exceeds pressure * coeff_backoff double max_probe_{0.01}; double max_backoff_{1.0}; double coeff_probe_{10}; double coeff_backoff_{20}; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/SwapFree.cpp000066400000000000000000000034741362162667600201260ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/SwapFree.h" #include "oomd/Log.h" #include "oomd/PluginRegistry.h" #include "oomd/include/Assert.h" namespace Oomd { REGISTER_PLUGIN(swap_free, SwapFree::create); int SwapFree::init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& args) { if (args.find("threshold_pct") != args.end()) { threshold_pct_ = std::stoi(args.at("threshold_pct")); } else { OLOG << "Argument=threshold_pct not present"; return 1; } // Success return 0; } Engine::PluginRet SwapFree::run(OomdContext& ctx) { auto& system_ctx = ctx.getSystemContext(); uint64_t swaptotal = system_ctx.swaptotal; uint64_t swapused = system_ctx.swapused; const uint64_t swapthres = swaptotal * threshold_pct_ / 100; if ((swaptotal - swapused) < swapthres) { OLOG << "SwapFree " << (swaptotal - swapused) / 1024 / 1024 << "MB is smaller than the threshold of " << swapthres / 1024 / 1024 << "MB, total swap is " << swaptotal / 1024 / 1024 << "MB"; return Engine::PluginRet::CONTINUE; } else { return Engine::PluginRet::STOP; } } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/SwapFree.h000066400000000000000000000023411362162667600175630ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include "oomd/engine/BasePlugin.h" namespace Oomd { class SwapFree : public Oomd::Engine::BasePlugin { public: int init( Engine::MonitoredResources& /* unused */, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static SwapFree* create() { return new SwapFree(); } ~SwapFree() = default; private: int threshold_pct_; // will be assigned in init() std::string swaps_location_; }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/systemd/000077500000000000000000000000001362162667600173665ustar00rootroot00000000000000oomd-0.3.1/src/oomd/plugins/systemd/BaseSystemdPlugin.cpp000066400000000000000000000061241362162667600234770ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/systemd/BaseSystemdPlugin.h" #include #include "oomd/Log.h" #include "oomd/util/ScopeGuard.h" namespace Oomd { /* Communicate with systemd over DBUS interface. Supports only methods with "ss" signature. see https://www.freedesktop.org/wiki/Software/systemd/dbus/ for details. returns false in case of any errors. */ bool BaseSystemdPlugin::talkToSystemdManager( const std::string& method, const std::string& service, const std::string& mode) { ::sd_bus_error error = SD_BUS_ERROR_NULL; ::sd_bus_message* m = nullptr; ::sd_bus* bus = nullptr; const char* path; int r; OOMD_SCOPE_EXIT { ::sd_bus_error_free(&error); ::sd_bus_message_unref(m); ::sd_bus_close(bus); ::sd_bus_unref(bus); }; /* Connect to the system bus */ r = ::sd_bus_open_system(&bus); if (r < 0) { OLOG << "Failed to connect to system bus: " << strerror(-r); return false; } if (bus == nullptr) { OLOG << "Failed to connect to system bus: bus is null"; return false; }; /* Issue the method call and store the respons message in m */ r = ::sd_bus_call_method( bus, "org.freedesktop.systemd1", /* service to contact */ "/org/freedesktop/systemd1", /* object path */ "org.freedesktop.systemd1.Manager", /* interface name */ method.c_str(), /* method name */ &error, /* object to return error in */ &m, /* return message on success */ "ss", /* input signature */ service.c_str(), /* first argument */ mode.c_str()); /* second argument */ if (r < 0) { OLOG << "Failed to issue method call: " << error.message; return false; } if (m == nullptr) { OLOG << "Failed to issue method call: return message is null"; return false; } /* Parse the response message */ r = ::sd_bus_message_read(m, "o", &path); if (r < 0) { OLOG << "Failed to parse response message: " << strerror(-r); return false; } OLOG << "Queued service job as " << path; return true; } bool BaseSystemdPlugin::restartService(const std::string& service) { OLOG << "Restarting systemd service \"" << service << "\""; return talkToSystemdManager("RestartUnit", service); } bool BaseSystemdPlugin::stopService(const std::string& service) { OLOG << "Stopping systemd service \"" << service << "\""; return talkToSystemdManager("StopUnit", service); } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/systemd/BaseSystemdPlugin.h000066400000000000000000000025121362162667600231410ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/engine/BasePlugin.h" namespace Oomd { /* * This abstract base class provides an overridable set of methods that * allow us to request some actions from systemd over DBUS. * Main reason this exists is mocking things for unittests. */ class BaseSystemdPlugin : public Oomd::Engine::BasePlugin { protected: virtual bool talkToSystemdManager( const std::string& method, const std::string& service, const std::string& mode = "replace"); virtual bool restartService(const std::string& service); virtual bool stopService(const std::string& service); }; } // namespace Oomd oomd-0.3.1/src/oomd/plugins/systemd/SystemdPluginsTest.cpp000066400000000000000000000046621362162667600237340ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "oomd/OomdContext.h" #include "oomd/plugins/systemd/BaseSystemdPlugin.h" #include "oomd/plugins/systemd/SystemdRestart.h" using namespace Oomd; using namespace testing; namespace Oomd { class BaseSystemdPluginMock : public BaseSystemdPlugin { public: bool restartService(const std::string& service) override { restarted = service; return true; } bool stopService(const std::string& service) override { stopped = service; return true; } bool talkToSystemdManager( const std::string& /* unused */, const std::string& /* unused */, const std::string& /* unused */) override { return true; } std::string restarted; std::string stopped; }; } // namespace Oomd TEST(SystemdRestart, RestartService) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["service"] = "some.service"; args["post_action_delay"] = "0"; args["dry"] = "false"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); OomdContext ctx; EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_EQ(plugin->restarted, "some.service"); } TEST(SystemdRestart, RestartServiceDry) { auto plugin = std::make_shared>(); ASSERT_NE(plugin, nullptr); Engine::MonitoredResources resources; Engine::PluginArgs args; args["service"] = "some.service"; args["post_action_delay"] = "0"; args["dry"] = "true"; ASSERT_EQ(plugin->init(resources, std::move(args)), 0); OomdContext ctx; EXPECT_EQ(plugin->run(ctx), Engine::PluginRet::STOP); EXPECT_EQ(plugin->restarted.size(), 0); } oomd-0.3.1/src/oomd/plugins/systemd/SystemdRestart-inl.h000066400000000000000000000045431362162667600233220ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/systemd/SystemdRestart.h" #include #include "oomd/Log.h" #include "oomd/Stats.h" namespace Oomd { template int SystemdRestart::init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) { if (args.find("service") != args.end()) { const std::string& service = args.at("service"); if (service.empty()) { OLOG << "Argument=service is empty"; } service_ = service; } else { OLOG << "Argument=service not present"; return 1; } if (args.find("post_action_delay") != args.end()) { int val = std::stoi(args.at("post_action_delay")); if (val < 0) { OLOG << "Argument=post_action_delay must be non-negative"; return 1; } post_action_delay_ = val; } if (args.find("dry") != args.end()) { const std::string& val = args.at("dry"); if (val == "true" || val == "True" || val == "1") { dry_ = true; } } Oomd::setStat(kRestartsKey, 0); // Success return 0; } template Engine::PluginRet SystemdRestart::run(OomdContext& /* unused */) { bool ret; if (dry_) { OLOG << "DRY-RUN: restarting service " << service_; ret = true; } else { ret = Base::restartService(service_); } if (ret) { std::ostringstream oss; oss << "restarted systemd service=" << service_ << (dry_ ? " (dry)" : ""); OOMD_KMSG_LOG(oss.str(), "oomd kill"); Oomd::incrementStat(kRestartsKey, 1); std::this_thread::sleep_for(std::chrono::seconds(post_action_delay_)); return Engine::PluginRet::STOP; } else { return Engine::PluginRet::CONTINUE; } } } // namespace Oomd oomd-0.3.1/src/oomd/plugins/systemd/SystemdRestart.cpp000066400000000000000000000015701362162667600230720ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/plugins/systemd/SystemdRestart.h" #include "oomd/PluginRegistry.h" namespace Oomd { REGISTER_PLUGIN(systemd_restart, SystemdRestart<>::create); } oomd-0.3.1/src/oomd/plugins/systemd/SystemdRestart.h000066400000000000000000000025611362162667600225400ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "oomd/plugins/systemd/BaseSystemdPlugin.h" namespace Oomd { template class SystemdRestart : public Base { public: int init( Engine::MonitoredResources& resources, const Engine::PluginArgs& args) override; Engine::PluginRet run(OomdContext& /* unused */) override; static SystemdRestart* create() { return new SystemdRestart(); } ~SystemdRestart() = default; private: std::string service_; int post_action_delay_{15}; bool dry_{false}; static constexpr auto kRestartsKey = "oomd.restarts"; }; } // namespace Oomd #include "oomd/plugins/systemd/SystemdRestart-inl.h" oomd-0.3.1/src/oomd/util/000077500000000000000000000000001362162667600151725ustar00rootroot00000000000000oomd-0.3.1/src/oomd/util/Fixture.cpp000066400000000000000000000107231362162667600173270ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include "oomd/util/Fixture.h" #include "oomd/util/Util.h" constexpr auto kFixturesDirTemplate = "__oomd_fixtures_XXXXXX"; namespace Oomd { // static Fixture::DirEntryPair Fixture::makeFile( const std::string& name, const std::string& content) { return {name, DirEntry([content](const std::string& path, const std::string& name) { writeChecked(path + "/" + name, content); })}; } // static Fixture::DirEntryPair Fixture::makeDir( const std::string& name, std::unordered_map entries) { return {name, DirEntry([entries](const std::string& path, const std::string& name) { mkdirsChecked(name, path); const auto newPath = path + "/" + name; for (const auto& kv : entries) { kv.second.materialize(newPath, kv.first); } })}; } const char* getTempDir() { if (std::getenv("TMPDIR") != nullptr) { return std::getenv("TMPDIR"); } else if (std::getenv("TMP") != nullptr) { return std::getenv("TMP"); } else if (std::getenv("TEMP") != nullptr) { return std::getenv("TEMP"); } else if (std::getenv("TEMPDIR") != nullptr) { return std::getenv("TEMPDIR"); } else { return "/tmp"; } } // static std::string Fixture::mkdtempChecked() { std::array temp; ::snprintf( temp.data(), temp.size(), "%s/%s", getTempDir(), kFixturesDirTemplate); if (::mkdtemp(temp.data()) == nullptr) { std::array buf; buf[0] = '\0'; throw std::runtime_error( std::string(temp.data()) + ": mkdtemp failed: " + ::strerror_r(errno, buf.data(), buf.size())); } return temp.data(); } // static void Fixture::mkdirsChecked( const std::string& path, const std::string& prefix) { auto dirs = Util::split(path, '/'); std::string prefix_path; // two slashes after each component prefix_path.reserve(path.size() + prefix.size() + 2); if (path.size() && path[0] == '/') { prefix_path += "/"; } else if (prefix != "") { prefix_path += prefix + "/"; } for (const auto& dir : dirs) { if (dir == "") { continue; } prefix_path += dir + "/"; if (::mkdir(prefix_path.c_str(), 0777) == -1 && errno != EEXIST) { std::array buf; buf[0] = '\0'; throw std::runtime_error( prefix_path + ": mkdir failed: " + ::strerror_r(errno, buf.data(), buf.size())); } } } // static void Fixture::writeChecked( const std::string& path, const std::string& content) { std::array buf; buf[0] = '\0'; auto fd = ::open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); if (fd < 0) { throw std::runtime_error( path + ": open failed: " + ::strerror_r(errno, buf.data(), buf.size())); } auto ret = Util::writeFull(fd, content.c_str(), content.size()); ::close(fd); if (ret < 0) { throw std::runtime_error( path + ": write failed: " + ::strerror_r(errno, buf.data(), buf.size())); } if ((size_t)ret != content.size()) { throw std::runtime_error( path + ": write failed: not all bytes are written"); } } int rm(const char* path, const struct stat*, int, struct FTW*) { return ::remove(path); } // static void Fixture::rmrChecked(const std::string& path) { std::array buf; buf[0] = '\0'; if (::nftw(path.c_str(), rm, 10, FTW_DEPTH | FTW_PHYS) == -1) { switch (errno) { case ENOENT: return; default: throw std::runtime_error( path + ": remove failed: " + ::strerror_r(errno, buf.data(), buf.size())); } } } } // namespace Oomd oomd-0.3.1/src/oomd/util/Fixture.h000066400000000000000000000044151362162667600167750ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include #include #include namespace Oomd { // Fixture is a utility class for unit tests to encode their fixture files and // materialize them in run time in some temporary directory. class Fixture { public: typedef std::function materializeFunc; // Dirent to be materialized into a file or directory class DirEntry { public: explicit DirEntry(materializeFunc materialize) : materialize_(materialize) {} void materialize(const std::string& path, const std::string& name) const { materialize_(path, name); } private: const materializeFunc materialize_; }; // Helper functions to create different named DirEntries so that we can // represent a directory tree with initializer_list in a single statement. typedef std::pair DirEntryPair; static DirEntryPair makeFile( const std::string& name, const std::string& content = ""); static DirEntryPair makeDir( const std::string& name, std::unordered_map entries = {}); // utility functions that interact with file system static std::string mkdtempChecked(); static void mkdirsChecked( const std::string& path, const std::string& prefix = ""); static void writeChecked(const std::string& path, const std::string& content); static void rmrChecked(const std::string& path); }; } // namespace Oomd oomd-0.3.1/src/oomd/util/FixtureTest.cpp000066400000000000000000000126121362162667600201660ustar00rootroot00000000000000// Copyright 2004-present Facebook. All Rights Reserved. #include #include #include #include #include #include #include #include #include #include using namespace Oomd; using namespace testing; class FixtureTest : public ::testing::Test { protected: // remove all temp directories void TearDown() override { DIR* dir; std::string failureMsg = ""; for (const auto& tempDir : tempDirs_) { dir = ::opendir(tempDir.c_str()); if (dir != nullptr) { ::closedir(dir); // call `rm -r '$tempDir'` to remove recursively const auto& cmd = "rm -r '" + tempDir + "'"; auto stream = ::popen(cmd.c_str(), "r"); if (stream != nullptr) { ::pclose(stream); } dir = ::opendir(tempDir.c_str()); if (dir != nullptr || errno != ENOENT) { failureMsg += " " + tempDir; } } } if (failureMsg != "") { FAIL() << "remove failed:" + failureMsg; } } std::unordered_set tempDirs_; }; TEST_F(FixtureTest, TestMkdtempChecked) { DIR* dir; std::string tempDir; for (int i = 0; i < 100; i++) { EXPECT_NO_THROW(tempDir = Fixture::mkdtempChecked()); // directory exists EXPECT_TRUE((dir = ::opendir(tempDir.c_str())) && ::closedir(dir) == 0); // directory not already seen EXPECT_TRUE(tempDirs_.find(tempDir) == tempDirs_.end()); tempDirs_.insert(tempDir); } } TEST_F(FixtureTest, TestMkdirsChecked) { DIR* dir; std::string tempDir; std::string path; EXPECT_NO_THROW(tempDir = Fixture::mkdtempChecked()); tempDirs_.insert(tempDir); // nothing should happen EXPECT_NO_THROW(Fixture::mkdirsChecked(tempDir)); // should ignore repeated slashes EXPECT_NO_THROW(Fixture::mkdirsChecked("A//B///C/", tempDir)); path = tempDir + "/A/B/C"; EXPECT_TRUE((dir = ::opendir(path.c_str())) && ::closedir(dir) == 0); // should create D without touching A EXPECT_NO_THROW(Fixture::mkdirsChecked("A/D", tempDir)); path = tempDir + "/A/B/C"; EXPECT_TRUE((dir = ::opendir(path.c_str())) && ::closedir(dir) == 0); path = tempDir + "/A/D"; EXPECT_TRUE((dir = ::opendir(path.c_str())) && ::closedir(dir) == 0); } TEST_F(FixtureTest, TestWriteChecked) { std::vector buf(1024); std::string tempDir; EXPECT_NO_THROW(tempDir = Fixture::mkdtempChecked()); tempDirs_.insert(tempDir); const auto filepath = tempDir + "/writetest.txt"; const std::string content = "Hello, Facebook!\nHello, world!\n"; const std::string newContent = "Bye, Facebook!\nHello again, world!\n"; // create and write EXPECT_NO_THROW(Fixture::writeChecked(filepath, content)); int fd = ::open(filepath.c_str(), O_RDONLY); EXPECT_GE(fd, 0); int bytes_read = Util::readFull(fd, buf.data(), content.length()); ::close(fd); EXPECT_EQ(bytes_read, content.length()); EXPECT_EQ(std::string(buf.data(), content.length()), content); // overwrite EXPECT_NO_THROW(Fixture::writeChecked(filepath, newContent)); fd = ::open(filepath.c_str(), O_RDONLY); EXPECT_GE(fd, 0); bytes_read = Util::readFull(fd, buf.data(), newContent.length()); ::close(fd); EXPECT_EQ(bytes_read, newContent.length()); EXPECT_EQ(std::string(buf.data(), newContent.length()), newContent); } TEST_F(FixtureTest, TestRmrChecked) { DIR* dir; std::string tempDir; EXPECT_NO_THROW({ // create directory tree tempDir = Fixture::mkdtempChecked(); tempDirs_.insert(tempDir); Fixture::mkdirsChecked("A/B/C", tempDir); Fixture::mkdirsChecked("D/E", tempDir); Fixture::writeChecked(tempDir + "/file1", "file1 content"); Fixture::writeChecked(tempDir + "/A/file2", "file2 content"); Fixture::writeChecked(tempDir + "/D/E/file3", "file3 content"); Fixture::rmrChecked(tempDir); }); tempDirs_.erase(tempDir); // check file does not exist dir = ::opendir(tempDir.c_str()); if (dir != nullptr) { ::closedir(dir); FAIL() << "Failed to remove tempDir: " + tempDir; } else { EXPECT_TRUE(errno == ENOENT); } } bool verifyFile(const std::string& filepath, const std::string& content) { int fd = ::open(filepath.c_str(), O_RDONLY); if (fd < 0) { return false; } std::vector buf(content.size()); ssize_t count = Util::readFull(fd, buf.data(), content.size()); ::close(fd); return std::string(buf.data(), count) == content; } TEST_F(FixtureTest, TestMaterialize) { DIR* dir; std::string tempDir; EXPECT_NO_THROW(tempDir = Fixture::mkdtempChecked()); tempDirs_.insert(tempDir); const auto fixture = Fixture::makeDir( "root", {Fixture::makeDir( "A", {Fixture::makeDir("B", {Fixture::makeDir("C")}), Fixture::makeFile("file2", "file2 content")}), Fixture::makeDir( "D", {Fixture::makeDir( "E", {Fixture::makeFile("file3", "file3 content")})}), Fixture::makeFile("file1", "file1 content")}); EXPECT_NO_THROW(fixture.second.materialize(tempDir, fixture.first)); auto root = tempDir + "/" + "root"; auto path = root + "/A/B/C"; EXPECT_TRUE((dir = ::opendir(path.c_str())) && ::closedir(dir) == 0); path = root + "/A/file2"; EXPECT_TRUE(verifyFile(path, "file2 content")); path = root + "/D/E/file3"; EXPECT_TRUE(verifyFile(path, "file3 content")); path = root + "/file1"; EXPECT_TRUE(verifyFile(path, "file1 content")); } oomd-0.3.1/src/oomd/util/Fs.cpp000066400000000000000000000443131362162667600162530ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/util/Fs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "oomd/include/Assert.h" #include "oomd/util/Util.h" namespace { enum class PsiFormat { MISSING = 0, // File is missing INVALID, // Don't recognize EXPERIMENTAL, // Experimental format UPSTREAM, // Upstream kernel format }; PsiFormat getPsiFormat(const std::vector& lines) { if (lines.size() == 0) { return PsiFormat::MISSING; } const auto& first = lines[0]; if (Oomd::Util::startsWith("some", first) && lines.size() >= 2) { return PsiFormat::UPSTREAM; } else if (Oomd::Util::startsWith("aggr", first) && lines.size() >= 3) { return PsiFormat::EXPERIMENTAL; } else { return PsiFormat::INVALID; } } }; // namespace namespace Oomd { struct Fs::DirEnts Fs::readDir(const std::string& path, int flags) { DIR* d; struct Fs::DirEnts de; d = ::opendir(path.c_str()); if (!d) { return de; } while (struct dirent* dir = ::readdir(d)) { if (dir->d_name[0] == '.') { continue; } /* * Optimisation: Avoid doing lstat calls if kernfs gives us back d_type. * This actually can be pretty useful, since avoiding lstat()ing everything * can reduce oomd CPU usage by ~10% on a reasonably sized cgroup * hierarchy. */ if ((flags & DirEntFlags::DE_FILE) && dir->d_type == DT_REG) { de.files.push_back(dir->d_name); continue; } if ((flags & DirEntFlags::DE_DIR) && dir->d_type == DT_DIR) { de.dirs.push_back(dir->d_name); continue; } auto file = path + "/" + dir->d_name; struct stat buf; int ret = ::lstat(file.c_str(), &buf); if (ret == -1) { continue; } if ((flags & DirEntFlags::DE_FILE) && (buf.st_mode & S_IFREG)) { de.files.push_back(dir->d_name); } if ((flags & DirEntFlags::DE_DIR) && (buf.st_mode & S_IFDIR)) { de.files.push_back(dir->d_name); } } ::closedir(d); return de; } bool Fs::isDir(const std::string& path) { struct stat sb; if (!::stat(path.c_str(), &sb) && S_ISDIR(sb.st_mode)) { return true; } return false; } /* * Return if a string might have something special for fnmatch. * * This function is simple and can return false-positives, but not * false-negatives -- that is, true means "maybe", and false means false. * That's ok, since this is only used for optimisations. */ bool Fs::hasGlob(const std::string& s) { return s.find_first_of("*[?") != std::string::npos; } std::unordered_set Fs::resolveWildcardPath( const CgroupPath& cgpath) { std::string path = cgpath.absolutePath(); std::unordered_set ret; if (path.empty()) { return ret; } auto parts = Util::split(path, '/'); OCHECK_EXCEPT(!parts.empty(), "No parts in " + path); std::deque> queue; // Add initial path piece to begin search on. Start at root. queue.emplace_back("/", 0); // Perform a DFS on the entire search space. Note that we pattern // match at each level of the provided path to eliminate "dead" // branches. The algorithm is still O(N) but in practice this will // prevent us from enumerating every entry in the root filesystem. // // We choose DFS because we predict the FS tree is wider than it // is tall. DFS will use less space than BFS in this case because // it does not need to store every node at each level of the tree. while (!queue.empty()) { const auto front = queue.front(); // copy queue.pop_front(); // Optimisation: If there's no glob and we're not at the end, it must be // intended to be a single dir. It doesn't matter if it actually *is* in // reality, because if it doesn't exist we'll fail later on. if (front.second < parts.size() - 1 && !Fs::hasGlob(parts[front.second])) { queue.emplace_front( front.first + parts[front.second] + "/", front.second + 1); continue; } // We can't continue BFS if we've hit a regular file if (!isDir(front.first)) { continue; } auto de = readDir(front.first, DE_FILE | DE_DIR); de.files.reserve(de.files.size() + de.dirs.size()); de.files.insert(de.files.end(), de.dirs.begin(), de.dirs.end()); for (const auto& entry : de.files) { if (::fnmatch(parts[front.second].c_str(), entry.c_str(), 0) == 0) { if (front.second == parts.size() - 1) { // We have reached a leaf, add it to the return set ret.emplace(front.first + entry); } else if (front.second < parts.size() - 1) { // There are still more parts of the provided path to search. // // Note that we add the '/' at the end of the new path. This makes // the recursive case easier, as the recursive case need only // add the next part of the path on. Also note the 'emplace_front' // that makes the deque into a stack (thus the DFS). queue.emplace_front(front.first + entry + "/", front.second + 1); } } } } return ret; } void Fs::removePrefix(std::string& str, const std::string& prefix) { if (str.find(prefix) != std::string::npos) { // Strip the leading './' if it exists and we haven't been explicitly // told to strip it if (str.find("./") == 0 && prefix.find("./") != 0) { str.erase(0, 2); } str.erase(0, prefix.size()); } } /* Reads a file and returns a newline separated vector of strings */ std::vector Fs::readFileByLine(const std::string& path) { std::ifstream f(path, std::ios::in); if (!f.is_open()) { return {}; } std::string s; std::vector v; while (std::getline(f, s)) { v.push_back(std::move(s)); } return v; } std::vector Fs::readControllers(const std::string& path) { std::vector controllers; auto lines = readFileByLine(path + "/" + kControllersFile); if (!lines.size()) { return controllers; } controllers = Util::split(lines[0], ' '); return controllers; } std::vector Fs::getPids(const std::string& path, bool recursive) { std::vector pids; auto de = readDir(path, DE_FILE | DE_DIR); if (std::any_of(de.files.begin(), de.files.end(), [](const std::string& s) { return s == kProcsFile; })) { auto str_pids = readFileByLine(path + "/" + kProcsFile); for (const auto& sp : str_pids) { pids.push_back(std::stoi(sp)); } } if (recursive) { for (const auto& dir : de.dirs) { auto recursive_pids = getPids(path + "/" + dir, true); pids.insert(pids.end(), recursive_pids.begin(), recursive_pids.end()); } } return pids; } std::string Fs::pressureTypeToString(PressureType type) { switch (type) { case PressureType::SOME: return "some"; case PressureType::FULL: return "full"; } throw std::runtime_error("Invalid PressureType. Code should not be reached"); } ResourcePressure Fs::readRespressure( const std::string& path, PressureType type) { auto lines = readFileByLine(path); auto type_name = pressureTypeToString(type); size_t pressure_line_index = 0; switch (type) { case PressureType::SOME: pressure_line_index = 0; break; case PressureType::FULL: pressure_line_index = 1; break; } switch (getPsiFormat(lines)) { case PsiFormat::UPSTREAM: { // Upstream v4.16+ format // // some avg10=0.22 avg60=0.17 avg300=1.11 total=58761459 // full avg10=0.22 avg60=0.16 avg300=1.08 total=58464525 std::vector toks = Util::split(lines[pressure_line_index], ' '); OCHECK_EXCEPT( toks[0] == type_name, bad_control_file(path + ": invalid format")); std::vector avg10 = Util::split(toks[1], '='); OCHECK_EXCEPT( avg10[0] == "avg10", bad_control_file(path + ": invalid format")); std::vector avg60 = Util::split(toks[2], '='); OCHECK_EXCEPT( avg60[0] == "avg60", bad_control_file(path + ": invalid format")); std::vector avg300 = Util::split(toks[3], '='); OCHECK_EXCEPT( avg300[0] == "avg300", bad_control_file(path + ": invalid format")); std::vector total = Util::split(toks[4], '='); OCHECK_EXCEPT( total[0] == "total", bad_control_file(path + ": invalid format")); return ResourcePressure{ std::stof(avg10[1]), std::stof(avg60[1]), std::stof(avg300[1]), std::chrono::microseconds(std::stoull(total[1])), }; } case PsiFormat::EXPERIMENTAL: { // Old experimental format // // aggr 316016073 // some 0.00 0.03 0.05 // full 0.00 0.03 0.05 std::vector toks = Util::split(lines[pressure_line_index + 1], ' '); OCHECK_EXCEPT( toks[0] == type_name, bad_control_file(path + ": invalid format")); return ResourcePressure{ std::stof(toks[1]), std::stof(toks[2]), std::stof(toks[3]), std::nullopt, }; } case PsiFormat::MISSING: // Missing the control file throw bad_control_file(path + ": missing file"); case PsiFormat::INVALID: throw bad_control_file(path + ": invalid format"); } // To silence g++ compiler warning about enums throw std::runtime_error("Not all enums handled"); } int64_t Fs::readMemcurrent(const std::string& path) { if (path == "/") { auto meminfo = getMeminfo("/proc/meminfo"); return meminfo["MemTotal"] - meminfo["MemFree"]; } else { auto lines = readFileByLine(path + "/" + kMemCurrentFile); OCHECK_EXCEPT(lines.size() == 1, bad_control_file(path + ": missing file")); return static_cast(std::stoll(lines[0])); } } ResourcePressure Fs::readMempressure( const std::string& path, PressureType type) { if (path == "/") { try { return readRespressure("/proc/pressure/memory", type); } catch (const bad_control_file& e) { return readRespressure("/proc/mempressure", type); } } else { return readRespressure(path + "/" + kMemPressureFile, type); } } int64_t Fs::readMinMaxLowHigh( const std::string& path, const std::string& file) { auto lines = readFileByLine(path + "/" + file); OCHECK_EXCEPT(lines.size() == 1, bad_control_file(path + ": missing file")); if (lines[0] == "max") { return std::numeric_limits::max(); } return static_cast(std::stoll(lines[0])); } int64_t Fs::readMemlow(const std::string& path) { return Fs::readMinMaxLowHigh(path, kMemLowFile); } int64_t Fs::readMemhigh(const std::string& path) { return Fs::readMinMaxLowHigh(path, kMemHighFile); } int64_t Fs::readMemmax(const std::string& path) { return Fs::readMinMaxLowHigh(path, kMemMaxFile); } int64_t Fs::readMemhightmp(const std::string& path) { auto lines = readFileByLine(path + "/" + kMemHighTmpFile); OCHECK_EXCEPT(lines.size() == 1, bad_control_file(path + ": missing file")); auto tokens = Util::split(lines[0], ' '); OCHECK_EXCEPT( tokens.size() == 2, bad_control_file(path + ": invalid format")); if (tokens[0] == "max") { return std::numeric_limits::max(); } return static_cast(std::stoll(tokens[0])); } int64_t Fs::readMemmin(const std::string& path) { return Fs::readMinMaxLowHigh(path, kMemMinFile); } int64_t Fs::readSwapCurrent(const std::string& path) { auto lines = readFileByLine(path + "/" + kMemSwapCurrentFile); // The swap controller can be disabled via CONFIG_MEMCG_SWAP=n if (lines.size() == 1) { return static_cast(std::stoll(lines[0])); } else { return 0; } } std::unordered_map Fs::getVmstat( const std::string& path) { auto lines = readFileByLine(path); std::unordered_map map; char space{' '}; for (auto& line : lines) { std::stringstream ss(line); std::string item; // get key std::getline(ss, item, space); std::string key{item}; // insert value into map std::getline(ss, item, space); map[key] = static_cast(std::stoll(item)); } return map; } std::unordered_map Fs::getMeminfo( const std::string& path) { char name[256] = {0}; uint64_t val; std::unordered_map map; auto lines = readFileByLine(path); for (auto& line : lines) { int ret = sscanf(line.c_str(), "%255[^:]:%*[ \t]%" SCNu64 "%*s\n", name, &val); if (ret == 2) { map[name] = val * 1024; } } return map; } std::unordered_map Fs::getMemstatLike( const std::string& file) { char name[256] = {0}; uint64_t val; std::unordered_map map; auto lines = readFileByLine(file); for (const auto& line : lines) { int ret = sscanf(line.c_str(), "%255s %" SCNu64 "\n", name, &val); if (ret == 2) { map[name] = val; } } return map; } std::unordered_map Fs::getMemstat( const std::string& path) { return getMemstatLike(path + "/" + kMemStatFile); } ResourcePressure Fs::readIopressure( const std::string& path, PressureType type) { if (path == "/") { return readRespressure("/proc/pressure/io", type); } else { return readRespressure(path + "/" + kIoPressureFile, type); } } IOStat Fs::readIostat(const std::string& path) { const auto& io_stat_path = path + "/" + kIoStatFile; auto lines = readFileByLine(io_stat_path); std::vector io_stat; io_stat.reserve(lines.size()); for (const auto& line : lines) { // format // // 0:0 rbytes=0 wbytes=0 rios=0 wios=0 dbytes=0 dios=0 DeviceIOStat dev_io_stat; int major, minor; int ret = sscanf( line.c_str(), "%d:%d rbytes=%" SCNd64 " wbytes=%" SCNd64 " rios=%" SCNd64 " wios=%" SCNd64 " dbytes=%" SCNd64 " dios=%" SCNd64 "\n", &major, &minor, &dev_io_stat.rbytes, &dev_io_stat.wbytes, &dev_io_stat.rios, &dev_io_stat.wios, &dev_io_stat.dbytes, &dev_io_stat.dios); OCHECK_EXCEPT(ret == 8, bad_control_file(path + ": invalid format")); dev_io_stat.dev_id = std::to_string(major) + ":" + std::to_string(minor); io_stat.push_back(dev_io_stat); } return io_stat; } void Fs::writeMemhigh(const std::string& path, int64_t value) { char buf[1024]; buf[0] = '\0'; auto file_name = path + "/" + kMemHighFile; auto fd = ::open(file_name.c_str(), O_WRONLY); if (fd < 0) { throw bad_control_file( file_name + ": open failed: " + ::strerror_r(errno, buf, sizeof(buf))); } auto val_str = std::to_string(value); auto ret = Util::writeFull(fd, val_str.c_str(), val_str.size()); ::close(fd); if (ret < 0) { throw bad_control_file( file_name + ": write failed: " + ::strerror_r(errno, buf, sizeof(buf))); } } void Fs::writeMemhightmp( const std::string& path, int64_t value, std::chrono::microseconds duration) { char buf[1024]; buf[0] = '\0'; auto file_name = path + "/" + kMemHighTmpFile; auto fd = ::open(file_name.c_str(), O_WRONLY); if (fd < 0) { throw bad_control_file( file_name + ": open failed: " + ::strerror_r(errno, buf, sizeof(buf))); } auto val_str = std::to_string(value) + " " + std::to_string(duration.count()); auto ret = Util::writeFull(fd, val_str.c_str(), val_str.size()); ::close(fd); if (ret < 0) { throw bad_control_file( file_name + ": write failed: " + ::strerror_r(errno, buf, sizeof(buf))); } } int64_t Fs::getNrDyingDescendants(const std::string& path) { auto map = getMemstatLike(path + "/" + kCgroupStatFile); // Will return 0 for missing entries return map["nr_dying_descendants"]; } bool Fs::setxattr( const std::string& path, const std::string& attr, const std::string& val) { int ret = ::setxattr(path.c_str(), attr.c_str(), val.c_str(), val.size(), 0); if (ret == -1) { return false; } return true; } std::string Fs::getxattr(const std::string& path, const std::string& attr) { std::string val; int size = ::getxattr(path.c_str(), attr.c_str(), nullptr, 0); if (size <= 0) { return val; } val.resize(size); ::getxattr(path.c_str(), attr.c_str(), &val[0], val.size()); return val; } bool Fs::isUnderParentPath( const std::string& parent_path, const std::string& path) { if (parent_path.empty() || path.empty()) { return false; } auto parent_parts = Util::split(parent_path, '/'); auto path_parts = Util::split(path, '/'); int i = 0; if (path_parts.size() < parent_parts.size()) { return false; } for (const auto& parts : parent_parts) { if (path_parts[i] != parts) { return false; } i++; } return true; } std::string Fs::getCgroup2MountPoint(const std::string& path) { auto lines = readFileByLine(path); for (auto& line : lines) { auto parts = Util::split(line, ' '); if (parts.size() > 2) { if (parts[2] == "cgroup2") { return parts[1] + '/'; } } } return ""; } DeviceType Fs::getDeviceType( const std::string& dev_id, const std::string& path) { const auto deviceTypeFile = path + "/" + dev_id + "/" + kDeviceTypeDir + "/" + kDeviceTypeFile; auto lines = readFileByLine(deviceTypeFile); if (lines.size() == 1) { if (lines[0] == "1") { return DeviceType::HDD; } else if (lines[0] == "0") { return DeviceType::SSD; } } throw bad_control_file(deviceTypeFile + ": invalid format"); } } // namespace Oomd oomd-0.3.1/src/oomd/util/Fs.h000066400000000000000000000135051362162667600157170ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include #include "oomd/include/CgroupPath.h" #include "oomd/include/Types.h" namespace Oomd { class Fs { public: class bad_control_file : public std::runtime_error { public: explicit bad_control_file(const std::string& msg) : std::runtime_error(msg) {} explicit bad_control_file(const char* msg) : std::runtime_error(msg) {} }; static constexpr auto kControllersFile = "cgroup.controllers"; static constexpr auto kSubtreeControlFile = "cgroup.subtree_control"; static constexpr auto kProcsFile = "cgroup.procs"; static constexpr auto kMemCurrentFile = "memory.current"; static constexpr auto kMemPressureFile = "memory.pressure"; static constexpr auto kMemLowFile = "memory.low"; static constexpr auto kMemHighFile = "memory.high"; static constexpr auto kMemMaxFile = "memory.max"; static constexpr auto kMemHighTmpFile = "memory.high.tmp"; static constexpr auto kMemMinFile = "memory.min"; static constexpr auto kMemStatFile = "memory.stat"; static constexpr auto kCgroupStatFile = "cgroup.stat"; static constexpr auto kMemSwapCurrentFile = "memory.swap.current"; static constexpr auto kIoPressureFile = "io.pressure"; static constexpr auto kIoStatFile = "io.stat"; static constexpr auto kDeviceTypeDir = "queue"; static constexpr auto kDeviceTypeFile = "rotational"; struct DirEnts { std::vector dirs; std::vector files; }; enum DirEntFlags { DE_FILE = 1, DE_DIR = (1 << 1), }; enum class PressureType { SOME = 0, FULL, }; /* * Reads a directory and returns the names of the requested entry types * Won't return any dotfiles (including ./ and ../) */ static struct DirEnts readDir(const std::string& path, int flags); /* * Checks if @param path is a directory */ static bool isDir(const std::string& path); /* * Takes a fully qualified and wildcarded path and returns a set of * resolved fully qualified paths. */ static std::unordered_set resolveWildcardPath( const CgroupPath& path); /* * Path aware prefix removal. * * If @param str begins with './', those two characters will be stripped. */ static void removePrefix(std::string& str, const std::string& prefix); /* Reads a file and returns a newline separated vector of strings */ static std::vector readFileByLine(const std::string& path); static std::vector readControllers(const std::string& path); static std::vector getPids( const std::string& path, bool recursive = false); static std::string pressureTypeToString(PressureType type); /* Helpers to read PSI files */ static ResourcePressure readRespressure( const std::string& path, PressureType type = PressureType::FULL); static int64_t readMemcurrent(const std::string& path); static ResourcePressure readMempressure( const std::string& path, PressureType type = PressureType::FULL); static int64_t readMinMaxLowHigh( const std::string& path, const std::string& file); static int64_t readMemlow(const std::string& path); static int64_t readMemhigh(const std::string& path); static int64_t readMemmax(const std::string& path); static int64_t readMemhightmp(const std::string& path); static int64_t readMemmin(const std::string& path); static int64_t readSwapCurrent(const std::string& path); static ResourcePressure readIopressure( const std::string& path, PressureType type = PressureType::FULL); static void writeMemhigh(const std::string& path, int64_t value); static void writeMemhightmp( const std::string& path, int64_t value, std::chrono::microseconds duration); static int64_t getNrDyingDescendants(const std::string& path); static IOStat readIostat(const std::string& path); static std::unordered_map getVmstat( const std::string& path = "/proc/vmstat"); static std::unordered_map getMeminfo( const std::string& path = "/proc/meminfo"); static std::unordered_map getMemstat( const std::string& path); // Return root part of cgroup2 from /proc/mounts/ static std::string getCgroup2MountPoint( const std::string& path = "/proc/mounts"); // Check if path point to parent path or somewhere inside parent path static bool isUnderParentPath( const std::string& parent_path, const std::string& path); /* Getters and setters to set xattr values */ static bool setxattr( const std::string& path, const std::string& attr, const std::string& val); static std::string getxattr(const std::string& path, const std::string& attr); // Return if device is SSD or HDD given its id in : format static DeviceType getDeviceType( const std::string& dev_id, const std::string& path = "/sys/dev/block"); static bool hasGlob(const std::string& s); private: static std::unordered_map getMemstatLike( const std::string& file); }; } // namespace Oomd oomd-0.3.1/src/oomd/util/FsTest.cpp000066400000000000000000000265221362162667600171150ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "oomd/fixtures/FsFixture.h" #include "oomd/util/Fs.h" #include "oomd/util/Util.h" using namespace Oomd; using namespace testing; class FsTest : public ::testing::Test { protected: void SetUp() override { try { fixture_.materialize(); } catch (const std::exception& e) { FAIL() << "SetUpTestSuite: " << e.what(); } } void TearDown() override { try { fixture_.teardown(); } catch (const std::exception& e) { FAIL() << "TearDownTestSuite: " << e.what(); } } FsFixture fixture_{}; }; TEST_F(FsTest, FindDirectories) { auto dir = fixture_.fsDataDir(); auto de = Fs::readDir(dir, Fs::DE_DIR); ASSERT_EQ(de.dirs.size(), 4); EXPECT_THAT(de.dirs, Contains(std::string("dir1"))); EXPECT_THAT(de.dirs, Contains(std::string("dir2"))); EXPECT_THAT(de.dirs, Contains(std::string("dir3"))); EXPECT_THAT(de.dirs, Contains(std::string("wildcard"))); EXPECT_THAT(de.dirs, Not(Contains(std::string("dir21")))); EXPECT_THAT(de.dirs, Not(Contains(std::string("dir22")))); } TEST_F(FsTest, IsDir) { auto dir = fixture_.fsDataDir(); EXPECT_TRUE(Fs::isDir(dir + "/dir1")); EXPECT_FALSE(Fs::isDir(dir + "/dir1/stuff")); EXPECT_FALSE(Fs::isDir(dir + "/NOTINFS")); } TEST_F(FsTest, RemovePrefix) { std::string s = "long string like this"; Fs::removePrefix(s, "long string "); EXPECT_EQ(s, "like this"); std::string ss = "random string"; Fs::removePrefix(ss, "asdf"); EXPECT_EQ(ss, "random string"); std::string sss = "asdf"; Fs::removePrefix(sss, "asdf"); EXPECT_EQ(sss, ""); std::string path = "./var/log/messages"; Fs::removePrefix(path, "var/log/"); EXPECT_EQ(path, "messages"); std::string path2 = "./var/log/messages"; Fs::removePrefix(path2, "./var/log/"); EXPECT_EQ(path2, "messages"); } TEST_F(FsTest, FindFiles) { auto dir = fixture_.fsDataDir(); auto de = Fs::readDir(dir, Fs::DE_FILE); ASSERT_EQ(de.files.size(), 4); EXPECT_THAT(de.files, Contains(std::string("file1"))); EXPECT_THAT(de.files, Contains(std::string("file2"))); EXPECT_THAT(de.files, Contains(std::string("file3"))); EXPECT_THAT(de.files, Contains(std::string("file4"))); EXPECT_THAT(de.files, Not(Contains(std::string("file5")))); } TEST_F(FsTest, ResolveWildcardedPath) { auto dir = fixture_.fsDataDir(); dir += "/wildcard"; CgroupPath wildcarded_path_some(dir, "/this/path/is*/going/to/be/long/file"); auto resolved = Fs::resolveWildcardPath(wildcarded_path_some); ASSERT_EQ(resolved.size(), 2); EXPECT_THAT(resolved, Contains(dir + "/this/path/is/going/to/be/long/file")); EXPECT_THAT( resolved, Contains(dir + "/this/path/isNOT/going/to/be/long/file")); CgroupPath wildcarded_path_all(dir, "/this/path/*/going/to/be/long/file"); resolved = Fs::resolveWildcardPath(wildcarded_path_all); ASSERT_EQ(resolved.size(), 3); EXPECT_THAT(resolved, Contains(dir + "/this/path/is/going/to/be/long/file")); EXPECT_THAT( resolved, Contains(dir + "/this/path/isNOT/going/to/be/long/file")); EXPECT_THAT(resolved, Contains(dir + "/this/path/WAH/going/to/be/long/file")); CgroupPath nonexistent_path(dir, "/not/a/valid/dir"); resolved = Fs::resolveWildcardPath(nonexistent_path); ASSERT_EQ(resolved.size(), 0); } TEST_F(FsTest, ReadFile) { auto file = fixture_.fsDataDir() + "/dir1/stuff"; auto lines = Fs::readFileByLine(file); ASSERT_EQ(lines.size(), 4); EXPECT_EQ(lines[0], "hello world"); EXPECT_EQ(lines[1], "my good man"); EXPECT_EQ(lines[2], ""); EXPECT_EQ(lines[3], "1"); } TEST_F(FsTest, ReadFileBad) { auto file = fixture_.fsDataDir() + "/ksldjfksdlfdsjf"; auto lines = Fs::readFileByLine(file); ASSERT_EQ(lines.size(), 0); } TEST_F(FsTest, GetPids) { auto dir = fixture_.cgroupDataDir(); auto pids = Fs::getPids(dir); EXPECT_EQ(pids.size(), 1); EXPECT_THAT(pids, Contains(123)); auto dir2 = dir + "/service1.service"; auto pids2 = Fs::getPids(dir2); EXPECT_EQ(pids2.size(), 2); EXPECT_THAT(pids2, Contains(456)); EXPECT_THAT(pids2, Contains(789)); auto pids_r = Fs::getPids(dir, true); EXPECT_EQ(pids_r.size(), 7); EXPECT_THAT(pids_r, Contains(123)); EXPECT_THAT(pids_r, Contains(456)); EXPECT_THAT(pids_r, Contains(789)); } TEST_F(FsTest, GetNrDying) { auto dir = fixture_.cgroupDataDir(); int64_t nr = Fs::getNrDyingDescendants(dir); EXPECT_EQ(nr, 27); } TEST_F(FsTest, ReadMemoryCurrent) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readMemcurrent(dir), 987654321); } TEST_F(FsTest, ReadMemoryLow) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readMemlow(dir), 333333); } TEST_F(FsTest, ReadMemoryMin) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readMemmin(dir), 666); } TEST_F(FsTest, ReadMemoryHigh) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readMemhigh(dir), 1000); } TEST_F(FsTest, ReadMemoryMax) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readMemmax(dir), 654); } TEST_F(FsTest, ReadMemoryHighTmp) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readMemhightmp(dir), 2000); } TEST_F(FsTest, ReadSwapCurrent) { auto dir = fixture_.cgroupDataDir(); EXPECT_EQ(Fs::readSwapCurrent(dir), 321321); } TEST_F(FsTest, ReadControllers) { auto dir = fixture_.cgroupDataDir(); auto controllers = Fs::readControllers(dir); ASSERT_EQ(controllers.size(), 4); EXPECT_THAT(controllers, Contains(std::string("cpu"))); EXPECT_THAT(controllers, Contains(std::string("io"))); EXPECT_THAT(controllers, Contains(std::string("memory"))); EXPECT_THAT(controllers, Contains(std::string("pids"))); EXPECT_THAT(controllers, Not(Contains(std::string("block")))); } TEST_F(FsTest, ReadMemoryPressure) { // v4.16+ upstream format auto dir = fixture_.cgroupDataDir(); auto pressure = Fs::readMempressure(dir); EXPECT_FLOAT_EQ(pressure.sec_10, 4.44); EXPECT_FLOAT_EQ(pressure.sec_60, 5.55); EXPECT_FLOAT_EQ(pressure.sec_600, 6.66); // old experimental format auto dir2 = dir + "/service2.service"; auto pressure2 = Fs::readMempressure(dir2); EXPECT_FLOAT_EQ(pressure2.sec_10, 4.44); EXPECT_FLOAT_EQ(pressure2.sec_60, 5.55); EXPECT_FLOAT_EQ(pressure2.sec_600, 6.66); // old experimental format w/ debug info on auto dir3 = dir + "/service3.service"; auto pressure3 = Fs::readMempressure(dir3); EXPECT_FLOAT_EQ(pressure3.sec_10, 4.44); EXPECT_FLOAT_EQ(pressure3.sec_60, 5.55); EXPECT_FLOAT_EQ(pressure3.sec_600, 6.66); } TEST_F(FsTest, ReadMemoryPressureSome) { // v4.16+ upstream format auto dir = fixture_.cgroupDataDir(); auto pressure = Fs::readMempressure(dir, Fs::PressureType::SOME); EXPECT_FLOAT_EQ(pressure.sec_10, 1.11); EXPECT_FLOAT_EQ(pressure.sec_60, 2.22); EXPECT_FLOAT_EQ(pressure.sec_600, 3.33); // old experimental format auto dir2 = dir + "/service2.service"; auto pressure2 = Fs::readMempressure(dir2, Fs::PressureType::SOME); EXPECT_FLOAT_EQ(pressure2.sec_10, 1.11); EXPECT_FLOAT_EQ(pressure2.sec_60, 2.22); EXPECT_FLOAT_EQ(pressure2.sec_600, 3.33); } TEST_F(FsTest, GetVmstat) { auto vmstatfile = fixture_.fsVmstatFile(); auto vmstat = Fs::getVmstat(vmstatfile); EXPECT_EQ(vmstat["first_key"], 12345); EXPECT_EQ(vmstat["second_key"], 678910); EXPECT_EQ(vmstat["thirdkey"], 999999); // we expect the key is missing (ie default val = 0) EXPECT_EQ(vmstat["asdf"], 0); } TEST_F(FsTest, GetMeminfo) { auto meminfofile = fixture_.fsMeminfoFile(); auto meminfo = Fs::getMeminfo(meminfofile); EXPECT_EQ(meminfo.size(), 49); EXPECT_EQ(meminfo["SwapTotal"], 2097148 * 1024); EXPECT_EQ(meminfo["SwapFree"], 1097041 * 1024); EXPECT_EQ(meminfo["HugePages_Total"], 0); // we expect the key is missing (ie default val = 0) EXPECT_EQ(meminfo["asdf"], 0); } TEST_F(FsTest, GetMemstat) { auto dir = fixture_.cgroupDataDir(); auto meminfo = Fs::getMemstat(dir); EXPECT_EQ(meminfo.size(), 29); EXPECT_EQ(meminfo["anon"], 1294168064); EXPECT_EQ(meminfo["file"], 3870687232); EXPECT_EQ(meminfo["pglazyfree"], 0); // we expect the key is missing (ie default val = 0) EXPECT_EQ(meminfo["asdf"], 0); } TEST_F(FsTest, ReadIoPressure) { auto dir = fixture_.cgroupDataDir(); auto pressure = Fs::readIopressure(dir); EXPECT_FLOAT_EQ(pressure.sec_10, 4.45); EXPECT_FLOAT_EQ(pressure.sec_60, 5.56); EXPECT_FLOAT_EQ(pressure.sec_600, 6.67); } TEST_F(FsTest, ReadIoPressureSome) { auto dir = fixture_.cgroupDataDir(); auto pressure = Fs::readIopressure(dir, Fs::PressureType::SOME); EXPECT_FLOAT_EQ(pressure.sec_10, 1.12); EXPECT_FLOAT_EQ(pressure.sec_60, 2.23); EXPECT_FLOAT_EQ(pressure.sec_600, 3.34); } TEST_F(FsTest, IsUnderParentPath) { EXPECT_TRUE(Fs::isUnderParentPath("/sys/fs/cgroup/", "/sys/fs/cgroup/")); EXPECT_TRUE(Fs::isUnderParentPath("/sys/fs/cgroup/", "/sys/fs/cgroup/blkio")); EXPECT_FALSE(Fs::isUnderParentPath("/sys/fs/cgroup/", "/sys/fs/")); EXPECT_TRUE(Fs::isUnderParentPath("/", "/sys/")); EXPECT_FALSE(Fs::isUnderParentPath("/sys/", "/")); EXPECT_FALSE(Fs::isUnderParentPath("", "/sys/")); EXPECT_FALSE(Fs::isUnderParentPath("/sys/", "")); EXPECT_FALSE(Fs::isUnderParentPath("", "")); } TEST_F(FsTest, GetCgroup2MountPoint) { auto mountsfile = fixture_.fsMountsFile(); auto cgrouppath = Fs::getCgroup2MountPoint(mountsfile); EXPECT_EQ(cgrouppath, std::string("/sys/fs/cgroup/")); } TEST_F(FsTest, GetDeviceType) { auto fsDevDir = fixture_.fsDeviceDir(); try { auto ssd_type = Fs::getDeviceType("1:0", fsDevDir); EXPECT_EQ(ssd_type, DeviceType::SSD); auto hdd_type = Fs::getDeviceType("1:1", fsDevDir); EXPECT_EQ(hdd_type, DeviceType::HDD); } catch (const std::exception& e) { FAIL() << "Expect no exception but got: " << e.what(); } try { Fs::getDeviceType("1:2", fsDevDir); FAIL() << "Expected Fs::bad_control_file"; } catch (Fs::bad_control_file& e) { EXPECT_EQ( e.what(), fsDevDir + "/1:2/" + Fs::kDeviceTypeDir + "/" + Fs::kDeviceTypeFile + ": invalid format"); } catch (const std::exception& e) { FAIL() << "Expected Fs::bad_control_file but got: " << e.what(); } } TEST_F(FsTest, ReadIostat) { auto dir = fixture_.cgroupDataDir(); auto io_stat = Fs::readIostat(dir); EXPECT_EQ(io_stat.size(), 2); auto stat0 = io_stat[0]; EXPECT_EQ(stat0.dev_id, "1:10"); EXPECT_EQ(stat0.rbytes, 1111111); EXPECT_EQ(stat0.wbytes, 2222222); EXPECT_EQ(stat0.rios, 33); EXPECT_EQ(stat0.wios, 44); EXPECT_EQ(stat0.dbytes, 5555555555); EXPECT_EQ(stat0.dios, 6); auto stat1 = io_stat[1]; EXPECT_EQ(stat1.dev_id, "1:11"); EXPECT_EQ(stat1.rbytes, 2222222); EXPECT_EQ(stat1.wbytes, 3333333); EXPECT_EQ(stat1.rios, 44); EXPECT_EQ(stat1.wios, 55); EXPECT_EQ(stat1.dbytes, 6666666666); EXPECT_EQ(stat1.dios, 7); } oomd-0.3.1/src/oomd/util/ScopeGuard.h000066400000000000000000000026231362162667600174020ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include // Need two levels of indirection here for __LINE__ to correctly expand #define OOMD_CONCAT2(a, b) a##b #define OOMD_CONCAT(a, b) OOMD_CONCAT2(a, b) #define OOMD_ANON_VAR(str) OOMD_CONCAT(str, __LINE__) namespace Oomd { enum class ScopeGuardExit {}; class ScopeGuard { public: explicit ScopeGuard(std::function fn) { fn_ = fn; }; ~ScopeGuard() { if (fn_) { fn_(); } }; private: std::function fn_; }; inline ScopeGuard operator+(ScopeGuardExit, std::function fn) { return ScopeGuard(fn); } } // namespace Oomd #define OOMD_SCOPE_EXIT \ auto OOMD_ANON_VAR(SCOPE_EXIT_STATE) = ScopeGuardExit() + [&]() oomd-0.3.1/src/oomd/util/ScopeGuardTest.cpp000066400000000000000000000030271362162667600205740ustar00rootroot00000000000000/* * Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "oomd/util/ScopeGuard.h" #include using namespace Oomd; using namespace testing; TEST(ScopeGuardTest, InnerScope) { int x = 5; { OOMD_SCOPE_EXIT { x++; }; } EXPECT_EQ(x, 6); } TEST(ScopeGuardTest, FunctionReturn) { int x = 5; [&]() { OOMD_SCOPE_EXIT { x++; }; x++; return; }(); EXPECT_EQ(x, 7); } TEST(ScopeGuardTest, ExceptionContext) { int x = 5; try { [&]() { OOMD_SCOPE_EXIT { x++; }; throw std::runtime_error("exception"); }(); } catch (const std::exception&) { } EXPECT_EQ(x, 6); } TEST(ScopeGuardTest, MultipleGuards) { int x = 5; { OOMD_SCOPE_EXIT { x++; }; OOMD_SCOPE_EXIT { x++; }; } EXPECT_EQ(x, 7); } oomd-0.3.1/src/oomd/util/Util.cpp000066400000000000000000000106221362162667600166140ustar00rootroot00000000000000/* * Copyright (C) 2019-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "oomd/util/Util.h" #include #include #include static constexpr auto kWhitespaceChars = " \t\n\r"; namespace { void ltrim(std::string& s) { s.erase(0, s.find_first_not_of(kWhitespaceChars)); } void rtrim(std::string& s) { s.erase(s.find_last_not_of(kWhitespaceChars) + 1); } template ssize_t wrapFull(F f, int fd, T buf, size_t count) { ssize_t totalBytes = 0; ssize_t r; do { r = f(fd, buf, count); if (r == -1) { if (errno == EINTR) { continue; } return r; } totalBytes += r; buf += r; count -= r; } while (r != 0 && count); // 0 means EOF return totalBytes; } } // namespace namespace Oomd { int Util::parseSize(const std::string& input, int64_t* output) { bool is_neg = false; uint64_t size = 0; size_t pos = 0; auto istr = input; // lower case and get rid of spaces transform(istr.begin(), istr.end(), istr.begin(), tolower); auto new_end = std::remove_if(istr.begin(), istr.end(), isspace); istr.erase(new_end - istr.begin()); // pop off leading sign if (istr[0] == '+') { pos++; } else if (istr[0] == '-') { is_neg = true; pos++; } while (pos < istr.length()) { size_t unit_pos; size_t end_pos; unit_pos = istr.find_first_of("kmgt", pos); if (unit_pos == pos) { return -1; } if (unit_pos == std::string::npos) { unit_pos = istr.length(); } auto num = istr.substr(pos, unit_pos - pos); auto unit = istr.c_str()[unit_pos]; double v; try { v = std::stold(num, &end_pos); } catch (...) { return -1; } if (end_pos != num.length() || v < 0) { return -1; } switch (unit) { case '\0': break; case 'k': v *= 1ULL << 10; break; case 'm': v *= 1ULL << 20; break; case 'g': v *= 1ULL << 30; break; case 't': v *= 1ULL << 40; break; default: return -1; } size += v; pos = unit_pos + 1; } *output = is_neg ? -size : size; return 0; } int Util::parseSizeOrPercent( const std::string& input, int64_t* output, int64_t total) { try { if (input.size() > 0 && input.at(input.size() - 1) == '%') { int64_t pct = std::stoi(input.substr(0, input.size() - 1)); if (pct < 0 || pct > 100) { return -1; } *output = total * pct / 100; return 0; } else { int64_t v; size_t end_pos; // compat - a bare number is interpreted as megabytes v = std::stoll(input, &end_pos); if (end_pos == input.length()) { *output = v << 20; return 0; } if (Util::parseSize(input, &v) == 0) { *output = v; return 0; } return -1; } } catch (...) { return -1; } } std::vector Util::split(const std::string& line, char delim) { std::istringstream iss(line); std::string item; std::vector ret; while (std::getline(iss, item, delim)) { if (item.size()) { ret.push_back(std::move(item)); } } return ret; } bool Util::startsWith(const std::string& prefix, const std::string& to_search) { if (prefix.size() > to_search.size()) { return false; } for (size_t i = 0; i < prefix.size(); ++i) { if (prefix[i] != to_search[i]) { return false; } } return true; } void Util::trim(std::string& s) { rtrim(s); ltrim(s); } ssize_t Util::readFull(int fd, char* msg_buf, size_t count) { return wrapFull(::read, fd, msg_buf, count); } ssize_t Util::writeFull(int fd, const char* msg_buf, size_t count) { return wrapFull(::write, fd, msg_buf, count); } } // namespace Oomd oomd-0.3.1/src/oomd/util/Util.h000066400000000000000000000052501362162667600162620ustar00rootroot00000000000000/* * Copyright (C) 2019-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include namespace Oomd { /* * Class to hold various utility functions. * * NB: This file *cannot* take any dependency other than standard library * otherwise you will cause circular dependencies elsewhere in the codebase. */ class Util { public: /* * Parsing rules: * * "1.5G" : 1.5 gigabytes, outputs 1610612736 bytes * "1G 128M" : 1 gigabyte and 128 megabytes, outputs 1207959552 bytes * "4K 2048" : 4 kilobytes and 2048 bytes, outputs 6144 bytes * * Supports [kmgt] suffixes. * * @returns 0 on success, -1 on failure. Parsed output is passed out * through @param output */ static int parseSize(const std::string& input, int64_t* output); /* * Handles the following 3 cases and returns the first valid result: * * 1) `%` and returns the % of @param total * 2) `` and returns the value in bytes (parsed as megabytes) * 3) `suffix` and acts the same as @function parseSize * * @returns 0 on success, -1 on failure. Parsed output is passed out * through @param output */ static int parseSizeOrPercent(const std::string& input, int64_t* output, int64_t total); /* Split string into tokens by delim */ static std::vector split(const std::string& line, char delim); static bool startsWith( const std::string& prefix, const std::string& to_search); /* Trim spaces from a string */ static void trim(std::string& s); /* * Read and write helpers * * Note that this is not an atomic operation. Multiple syscalls may be * issued to complete this request, and if any syscall fails, the API * will return -1. * * @returns @param count on success, -1 on failure. Will never return * any values other than those two. */ static ssize_t readFull(int fd, char* msg_buf, size_t count); static ssize_t writeFull(int fd, const char* msg_buf, size_t count); }; } // namespace Oomd oomd-0.3.1/src/oomd/util/UtilTest.cpp000066400000000000000000000074351362162667600174640ustar00rootroot00000000000000/* * Copyright (C) 2019-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "oomd/util/Util.h" using namespace Oomd; using namespace testing; TEST(UtilTest, ParseSizeTest) { int64_t v; EXPECT_EQ(Util::parseSize("8192", &v), 0); EXPECT_EQ(v, 8192); EXPECT_EQ(Util::parseSize("8K", &v), 0); EXPECT_EQ(v, 8192); EXPECT_EQ(Util::parseSize("1.5M 32K 512", &v), 0); EXPECT_EQ(v, (1 << 20) * 3 / 2 + (1 << 10) * 32 + 512); EXPECT_EQ(Util::parseSize("1.5MK", &v), -1); EXPECT_EQ(Util::parseSize("??", &v), -1); EXPECT_EQ(Util::parseSize("+-123M", &v), -1); } TEST(UtilTest, ParseSizeOrPercentTest) { int64_t v; EXPECT_EQ(Util::parseSizeOrPercent("1%", &v, 100), 0); EXPECT_EQ(v, 1); EXPECT_EQ(Util::parseSizeOrPercent("5M", &v, 100), 0); EXPECT_EQ(v, 5 << 20); EXPECT_EQ(Util::parseSizeOrPercent("5", &v, 100), 0); EXPECT_EQ(v, 5 << 20); EXPECT_EQ(Util::parseSizeOrPercent("5K", &v, 100), 0); EXPECT_EQ(v, 5 << 10); EXPECT_NE(Util::parseSizeOrPercent("5%z", &v, 100), 0); } TEST(UtilTest, Split) { auto toks = Util::split("one by two", ' '); ASSERT_EQ(toks.size(), 3); EXPECT_THAT(toks, Contains("one")); EXPECT_THAT(toks, Contains("by")); EXPECT_THAT(toks, Contains("two")); toks = Util::split(" by two", ' '); ASSERT_EQ(toks.size(), 2); EXPECT_THAT(toks, Contains("by")); EXPECT_THAT(toks, Contains("two")); toks = Util::split(" by two", ' '); ASSERT_EQ(toks.size(), 2); EXPECT_THAT(toks, Contains("by")); EXPECT_THAT(toks, Contains("two")); toks = Util::split("one two three", ','); ASSERT_EQ(toks.size(), 1); EXPECT_EQ(toks[0], "one two three"); } TEST(UtilTest, StartsWith) { EXPECT_TRUE(Util::startsWith("prefix", "prefixThis!")); EXPECT_TRUE(Util::startsWith("x", "xx")); EXPECT_TRUE(Util::startsWith("", "xx")); EXPECT_TRUE(Util::startsWith("", "")); EXPECT_FALSE(Util::startsWith("prefix", "prefiyThat!")); EXPECT_FALSE(Util::startsWith("xx", "x")); EXPECT_FALSE(Util::startsWith("x", "")); } TEST(UtilTest, Trim) { std::string s = " sdf "; Util::trim(s); EXPECT_EQ(s, "sdf"); std::string s1 = " as df "; Util::trim(s1); EXPECT_EQ(s1, "as df"); std::string s2 = " asdf"; Util::trim(s2); EXPECT_EQ(s2, "asdf"); std::string s3 = "asdf "; Util::trim(s3); EXPECT_EQ(s3, "asdf"); std::string s4 = "asdf"; Util::trim(s4); EXPECT_EQ(s4, "asdf"); std::string s5 = ""; Util::trim(s5); EXPECT_EQ(s5, ""); std::string s6 = " \t \n"; Util::trim(s6); EXPECT_EQ(s6, ""); } TEST(UtilTest, ReadWriteFull) { int fd = ::syscall(SYS_memfd_create, "myfile", MFD_CLOEXEC); ASSERT_GE(fd, 0); // Write a bunch of data in std::string start(1234567, 'z'); EXPECT_EQ(Util::writeFull(fd, start.data(), start.size()), start.size()); // Seek back to beginning ::lseek(fd, 0, SEEK_SET); // Read data back out std::string end(12345678, 'x'); // Note the extra 8 EXPECT_EQ(Util::readFull(fd, end.data(), start.size()), start.size()); EXPECT_EQ(std::memcmp(start.data(), end.data(), start.size()), 0); } oomd-0.3.1/vcs_tagger.sh000077500000000000000000000004061362162667600151530ustar00rootroot00000000000000#!/bin/bash set -euo pipefail dir="$1" fallback="$2" # Go into provided dir so builds started from outside the project # directory still generate the right tags cd "$dir" &>/dev/null && git describe --abbrev=7 --dirty --tags 2>/dev/null || echo "$fallback"