{"id":23531687,"url":"https://github.com/cjrh/playpen","last_synced_at":"2025-05-14T14:34:17.430Z","repository":{"id":268044221,"uuid":"902604076","full_name":"cjrh/playpen","owner":"cjrh","description":"Program launcher with memory and cpu limits","archived":false,"fork":false,"pushed_at":"2025-01-15T14:27:43.000Z","size":601,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-11T09:01:38.292Z","etag":null,"topics":["cgr","cli","cpu-limit","memory-limit","systemd-run"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cjrh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-12-12T22:35:10.000Z","updated_at":"2025-03-11T13:53:48.000Z","dependencies_parsed_at":"2024-12-14T02:24:00.128Z","dependency_job_id":"b6dd13a3-ba97-40b1-981b-ed8c06113f34","html_url":"https://github.com/cjrh/playpen","commit_stats":null,"previous_names":["cjrh/playpen"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjrh%2Fplaypen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjrh%2Fplaypen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjrh%2Fplaypen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjrh%2Fplaypen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cjrh","download_url":"https://codeload.github.com/cjrh/playpen/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254160647,"owners_count":22024574,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cgr","cli","cpu-limit","memory-limit","systemd-run"],"created_at":"2024-12-25T22:18:37.084Z","updated_at":"2025-05-14T14:34:17.413Z","avatar_url":"https://github.com/cjrh.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"![playpen](playpen_transparent2.png)\n\n# playpen\nProgram launcher with memory and cpu limits\n\n## Overview\n\nI wanted a way to run a program with memory and CPU limits. It turns out\nthat `systemd-run` does exactly this, _and it doesn't require root_, but\nthe necessary parameters are many and confusing. So playpen wraps all that\nup in a simple CLI which is set up for the typical use cases I have.\n\n## CLI Docs\n\n```\n$ playpen -h\nUsage: playpen [OPTIONS] [COMMAND_AND_ARGS]...\n\nArguments:\n  [COMMAND_AND_ARGS]...  \n\nOptions:\n  -m, --memory-limit \u003cMEMORY_LIMIT\u003e                \n  -c, --cpu-limit \u003cCPU_LIMIT\u003e                      \n  -q, --quiet                                      \n      --capture-env \u003cCAPTURE_ENV\u003e  [default: false] [possible values: true, false]\n      --capture-path \u003cCAPTURE_PATH\u003e                [default: true] [possible values: true, false]\n  -h, --help                                       Print help\n  -V, --version                                    Print version\n```\n\n## Demo\n\nThis interactive python session is killed by the OOM killer because\nit exceeds the 50 MB memory limit given:\n\n```\n$ playpen -m 50M python3\nRunning as unit: run-u544.service; invocation ID: 0bf06fd9a32b4e619993845b58066edd\nPress ^] three times within 1s to disconnect TTY.\n\nPython 3.12.3 (main, Nov  6 2024, 18:32:19) [GCC 13.2.0] on linux\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n\u003e\u003e\u003e x = [0] * 2**30\n\nFinished with result: oom-kill\nMain processes terminated with: code=killed/status=KILL\nService runtime: 11.423s\nCPU time consumed: 103ms\nMemory peak: 50.0M\nMemory swap peak: 0B\n```\n\nThis event will also show up in the system logs:\n\n```journalctl\n$ sudo journalctl --since \"1 minute ago\"\nDec 14 17:13:12 kernel: python3 invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=200\nDec 14 17:13:12 kernel: CPU: 12 PID: 483880 Comm: python3 Tainted: P           O       6.8.0-50-generic #51-Ubuntu\nDec 14 17:13:12 kernel: Hardware name: XXXXXXXXXXXXXXXXXXXXXXXXXXXX, XXXXXXXXXXXXXXXXXXXX XXXXXXXXXX\n...\nDec 14 17:13:12 kernel: memory: usage 51200kB, limit 51200kB, failcnt 37\nDec 14 17:13:12 kernel: swap: usage 0kB, limit 0kB, failcnt 0\nDec 14 17:13:12 kernel: Memory cgroup stats for /user.slice/user-1000.slice/user@1000.service/app.slice/run-u...\n...\nDec 14 17:13:12 kernel: Tasks state (memory values in pages):\nDec 14 17:13:12 kernel: [  pid  ]   uid  tgid total_vm      rss rss_anon rss_file rss_shmem pgtables_bytes sw...\nDec 14 17:13:12 kernel: [ 483880]  1000 483880    73357    14530    12706     1824         0   180224        ...\nDec 14 17:13:12 kernel: oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=user.slice,mems_allowed=0...\nDec 14 17:13:12 kernel: Memory cgroup out of memory: Killed process 483880 (python3) total-vm:293428kB, anon-...\nDec 14 17:13:12 systemd[2765]: run-u820.service: A process of this unit has been killed by the OOM killer.\nDec 14 17:13:12 systemd[2765]: run-u820.service: Main process exited, code=killed, status=9/KILL\nDec 14 17:13:12 systemd[2765]: run-u820.service: Failed with result 'oom-kill'.\nDec 14 17:13:12 systemd[2765]: Failed to reset TTY ownership/access mode of /dev/pts/2 to 0:5, ignoring: Oper...\nDec 14 17:13:12 systemd[1]: user@1000.service: A process of this unit has been killed by the OOM killer.\n\n```\n\nIn data science workloads, it is common to run out of memory and have\nthe system start swapping, which can make the system unresponsive. With\nplaypen, you can set a memory limit to bound a process, which will be\nOOM-killed if it exceeds the limit. So you don't have to restart the\nmachine!\n\nA CPU limit can also be set:\n\n```\n$ playpen -m 50M -c 100% python3\n```\n\nFor CPU, \"100%\" means 1 core, \"200%\" means 2 cores, and so on. This can\nbe used to force a process to run on fewer than all available cores\non a machine if the process does not have a simple, built-in way to limit \nitself.\n\n## Capturing `$PATH` and the environment\n\nBy default, `playpen` captures the `$PATH`, sending this through to\nthe underlying `systemd-run` command. However, the environment is not\ncaptured by default, for safety reasons. This can result in errors if\nyour task invocation relies on environment variables in your calling\nenvironment. Note that `capture-env` supercedes `capture-path` if enabled,\nso if `capture-env` is on, `capture-path` is ignored.\n\nThis also applies to env vars supplied on the path:\n\n```bash\n$ ABC=123 playpen --capture-env=on -- bash -c 'env'\n\u003csnip\u003e\nABC=123\n\u003csnip\u003e\n```\n\nBut if the option is off, the `ABC` variable is not passed through.\nMy suggestion is that you should rather use a `.env` file and have\nyour application read that instead of relying on the live environment.\nThis is much safer because you won't get inadvertent leakage of\ninformation unintended for the child process.\n\nThere are some interactive use-cases where passing the entire environment\nis very convenient, and where playpen is really just used as a proxy\nfor an interactive command. Shells like python are an example of this,\nbut also build tools like `npm run dev`, where a dev server runs to \nserve a web application in a development environment. An invocation for\nthis looks like this:\n\n```bash\n$ playpen -m 4G --capture-env=on -- npm run dev\n```\n\nOccasionally this dev servers get memory leaks, making playpen more\nuseful ;).\n\n## Examples\n\n### Simple example\n\nWrapping a simple command, like `date`:\n\n```\n$ playpen -q date\nSat Dec 14 05:24:10 PM CET 2024\n```\n\nWorks just like the `date` command. The `-q` means \"quiet\" and we'll get\nto that later.\n\nIn the next example, we apply a 1K memory limit to the `date` command:\n\n```\n$ playpen -q -m 1K date\n```\n\nIn this case, there is no output because the process was OOM-killed. It\nturns out that `date` needs more than 1K of memory to run:\n\n```\n$ playpen -q -m 300K date\nSat Dec 14 05:26:40 PM CET 2024\n```\n\nWorks with 300K of memory. The `date` command is not very memory-hungry.\n\n### Quiet mode\n\nRemoving the `-q` flag, we can see the systemd-run output:\n\n```\n$ playpen -q date\nRunning as unit: run-u821.service; invocation ID: 5577f88c2d304ae3aee5607965cd0eed\nPress ^] three times within 1s to disconnect TTY.\nSat Dec 14 05:22:31 PM CET 2024\nFinished with result: success\nMain processes terminated with: code=exited/status=0\nService runtime: 5ms\nCPU time consumed: 1ms\nMemory peak: 256.0K\nMemory swap peak: 0B\n```\n\nInternally, `playpen` uses `systemd-run` to launch the process. The\n`--quiet` flag suppresses the systemd-run output. If `playpen` detects\nthat the output is a tty, it will include the `--pty` parameter to\n`systemd-run`.\n\n### Using `--` to separate `playpen` options from the command to run\n\nYou have to separate the `playpen` options from the command to run with\n`--`:\n\n```\n$ playpen -q -m 300K -- date --rfc-3339 s\n2024-12-14 17:29:40+01:00\n```\n\n### More complex example: calling Python\n\nAs shown earlier, you can start an interactive Python interpreter:\n\n```\n$ playpen -m 50M -q -- python3\nPython 3.12.3 (main, Nov  6 2024, 18:32:19) [GCC 13.2.0] on linux\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n\u003e\u003e\u003e print(123)\n123\n\u003e\u003e\u003e \n```\n\nSimple example of running a python script with a 50 MB memory limit:\n\n```\n$ playpen -m 50M -q -- python3 -c \"print(123)\"\n123\n```\n\nThe `--quiet` flag suppresses the systemd-run wrapper output. This is what\nyou see if you omit the `--quiet` flag:\n\n```\n$ playpen -m 50M -- python3 -c \"print(123)\"\nRunning as unit: run-u814.service; invocation ID: d0523f2f13df45968dd05a48000dbb67\nPress ^] three times within 1s to disconnect TTY.\n123\nFinished with result: success\nMain processes terminated with: code=exited/status=0\nService runtime: 31ms\nCPU time consumed: 26ms\nMemory peak: 256.0K\nMemory swap peak: 0B\n```\n\nThe output above is nearly all stderr, which means that the actual\nprogram output, in this case `123`, is on stdout and can therefore\nbe piped to another program. In the example below, we have our\nPython layer emit `123 123`, and then we use `tr` to replace the\nspace with a `+`:\n\n```\n$ playpen -m 50M -- python3 -c \"print('123 123')\" | tr ' ' '+'\nRunning as unit: run-u816.service; invocation ID: 36a565c23f024a3e9b26b79d9695f6be\n123+123\nFinished with result: success\nMain processes terminated with: code=exited/status=0\nService runtime: 14ms\nCPU time consumed: 14ms\nMemory peak: 512.0K\nMemory swap peak: 0B\n```\n\nNote that the `tr` operated only on the stdout.\n\nThe above example has an important difference from the first example: the\nmessage about `Press ^] three times within 1s to disconnect TTY` is not\nprinted. This is because the `playpen` command is no longer attached to a tty\non its stdout.\n\n### Pipelines\n\nYou can use `playpen` in a pipeline.\n\n```python\n# add_days.py\nimport sys\nfrom datetime import datetime as dt\nfrom datetime import timedelta\n\nfor line in sys.stdin:\n    value = dt.fromisoformat(line.strip())\n    output = value + timedelta(days=int(sys.argv[1]))\n    print(output.isoformat())\n```\n\nNow we can use it in a pipeline:\n\n```\n$ echo $(date --rfc-3339 s) \\\n      | playpen -m 50M -q --  python3 add_days.py 10 \\\n      | tr '[0-9]' 'X'\nXXXX-XX-XXTXX:XX:XX+XX:XX\n```\n\nOf course, our script is never going to hit the memory limit, but it's\na good example of how to use `playpen` in a pipeline.\n\n\n## Dependencies\n\nThis only works on Linux and requires the `systemd` service manager.\nIn particular, it uses the `systemd-run` command to launch the\nprocesses in a cgroup with the given limits. As such, `playpen`\nis a shallow wrapper around `systemd-run`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcjrh%2Fplaypen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcjrh%2Fplaypen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcjrh%2Fplaypen/lists"}