runsentry 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runsentry-0.1.0a1/LICENSE +21 -0
- runsentry-0.1.0a1/PKG-INFO +227 -0
- runsentry-0.1.0a1/README.md +202 -0
- runsentry-0.1.0a1/pyproject.toml +41 -0
- runsentry-0.1.0a1/setup.cfg +4 -0
- runsentry-0.1.0a1/src/runsentry/__init__.py +3 -0
- runsentry-0.1.0a1/src/runsentry/__main__.py +4 -0
- runsentry-0.1.0a1/src/runsentry/cli.py +128 -0
- runsentry-0.1.0a1/src/runsentry/execution.py +393 -0
- runsentry-0.1.0a1/src/runsentry/health.py +344 -0
- runsentry-0.1.0a1/src/runsentry/observation.py +451 -0
- runsentry-0.1.0a1/src/runsentry/output.py +158 -0
- runsentry-0.1.0a1/src/runsentry/telemetry.py +446 -0
- runsentry-0.1.0a1/src/runsentry/version.py +8 -0
- runsentry-0.1.0a1/src/runsentry/watch.py +375 -0
- runsentry-0.1.0a1/src/runsentry.egg-info/PKG-INFO +227 -0
- runsentry-0.1.0a1/src/runsentry.egg-info/SOURCES.txt +27 -0
- runsentry-0.1.0a1/src/runsentry.egg-info/dependency_links.txt +1 -0
- runsentry-0.1.0a1/src/runsentry.egg-info/entry_points.txt +2 -0
- runsentry-0.1.0a1/src/runsentry.egg-info/requires.txt +4 -0
- runsentry-0.1.0a1/src/runsentry.egg-info/top_level.txt +1 -0
- runsentry-0.1.0a1/tests/test_cli.py +208 -0
- runsentry-0.1.0a1/tests/test_health.py +390 -0
- runsentry-0.1.0a1/tests/test_import.py +5 -0
- runsentry-0.1.0a1/tests/test_observation.py +283 -0
- runsentry-0.1.0a1/tests/test_output.py +228 -0
- runsentry-0.1.0a1/tests/test_synthetic_scenarios.py +296 -0
- runsentry-0.1.0a1/tests/test_telemetry.py +445 -0
- runsentry-0.1.0a1/tests/test_watch.py +370 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RunSentry contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runsentry
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: A lightweight local health observer for long-running commands and Python jobs.
|
|
5
|
+
Author: RunSentry contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: System :: Monitoring
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: psutil>=5.9
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# RunSentry
|
|
27
|
+
|
|
28
|
+
A lightweight local observer for long-running commands.
|
|
29
|
+
|
|
30
|
+
Wrap any command without modifying it:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
runsentry run --name training -- python train.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
RunSentry records:
|
|
37
|
+
|
|
38
|
+
- process/resource activity;
|
|
39
|
+
- stdout/stderr activity;
|
|
40
|
+
- watched file/directory changes;
|
|
41
|
+
- conservative health state;
|
|
42
|
+
- local JSONL telemetry;
|
|
43
|
+
- final `summary.json`.
|
|
44
|
+
|
|
45
|
+
No daemon.
|
|
46
|
+
No cloud.
|
|
47
|
+
No automatic killing.
|
|
48
|
+
|
|
49
|
+
It is for developers, researchers, and local AI/agent users who want factual evidence
|
|
50
|
+
about commands that may stay active for minutes or hours.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
runsentry run \
|
|
54
|
+
--name demo \
|
|
55
|
+
--watch /tmp/runsentry-demo-output.txt \
|
|
56
|
+
-- python examples/demo_long_job.py
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Public alpha status
|
|
60
|
+
|
|
61
|
+
RunSentry is in conservative public alpha. The P0 scope is useful for local observation
|
|
62
|
+
and debugging, but it is not a production supervisor or a guaranteed stuck-process
|
|
63
|
+
detector.
|
|
64
|
+
|
|
65
|
+
## Current P0 capabilities
|
|
66
|
+
|
|
67
|
+
RunSentry P0 can:
|
|
68
|
+
|
|
69
|
+
- launch a command directly with `shell=False`;
|
|
70
|
+
- preserve the child command argv after an explicit `--` boundary;
|
|
71
|
+
- tee child stdout and stderr back to the terminal as byte streams;
|
|
72
|
+
- count stdout/stderr bytes and chunks without storing output content;
|
|
73
|
+
- observe process/resource facts with `psutil`;
|
|
74
|
+
- observe watched file/directory size, mtime, file count, and disk usage facts;
|
|
75
|
+
- write local `.runsentry/runs/<run_id>/telemetry.jsonl`;
|
|
76
|
+
- write local `.runsentry/runs/<run_id>/summary.json`;
|
|
77
|
+
- emit conservative health states:
|
|
78
|
+
- `STARTING`
|
|
79
|
+
- `HEALTHY`
|
|
80
|
+
- `QUIET`
|
|
81
|
+
- `SUSPECTED_STALL`
|
|
82
|
+
- `FAILED`
|
|
83
|
+
- `COMPLETE`
|
|
84
|
+
|
|
85
|
+
`SUSPECTED_STALL` is a conservative suspicion, not proof. Silence alone is not a stall.
|
|
86
|
+
No watch path is not stall evidence. Unavailable metrics reduce confidence.
|
|
87
|
+
|
|
88
|
+
## Install from source
|
|
89
|
+
|
|
90
|
+
RunSentry currently targets Python 3.10+ on macOS and Linux.
|
|
91
|
+
|
|
92
|
+
Current source installation:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python -m pip install -e .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For development tests:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m pip install -e ".[test]"
|
|
102
|
+
python -m pytest -q
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Future PyPI installation is planned, but the package has not been published to PyPI yet.
|
|
106
|
+
Until that happens, source installation is required.
|
|
107
|
+
|
|
108
|
+
## Basic usage
|
|
109
|
+
|
|
110
|
+
Always put the command to run after the explicit `--` boundary:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
runsentry run --name demo -- python3 -c "import time; time.sleep(2)"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Watch an output file or directory:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
runsentry run --name io-demo --watch out.log -- python3 job.py
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Use more than one watch path:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
runsentry run --watch out.log --watch output_dir -- python3 job.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Everything after `--` belongs to the child command unchanged:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
runsentry run --name outer -- python3 job.py --watch child-value
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
RunSentry does not invoke a shell. If you need shell syntax, launch the shell explicitly:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
runsentry run --name shell-demo -- bash -lc 'python3 job.py | tee output.log'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
In that case, the shell is the observed root process and shell quoting rules are your
|
|
141
|
+
responsibility.
|
|
142
|
+
|
|
143
|
+
## Output artifacts
|
|
144
|
+
|
|
145
|
+
By default, each run writes:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
.runsentry/
|
|
149
|
+
runs/
|
|
150
|
+
<run_id>/
|
|
151
|
+
telemetry.jsonl
|
|
152
|
+
summary.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Use `--output-dir` to place artifacts elsewhere:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
runsentry run --output-dir /tmp/runsentry-demo -- python3 job.py
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Telemetry is local. RunSentry does not upload data or contact a service.
|
|
162
|
+
|
|
163
|
+
RunSentry stores stdout/stderr activity counters, not stdout/stderr content. Command argv
|
|
164
|
+
is stored because it is part of the factual launch record, so avoid putting secrets in
|
|
165
|
+
command arguments.
|
|
166
|
+
|
|
167
|
+
Generated `.runsentry/` artifacts are ignored by this repository and should normally
|
|
168
|
+
stay out of commits.
|
|
169
|
+
|
|
170
|
+
## Health states
|
|
171
|
+
|
|
172
|
+
- `STARTING`: the run is inside the initial observation window.
|
|
173
|
+
- `HEALTHY`: recent positive activity was observed.
|
|
174
|
+
- `QUIET`: little activity is visible, but evidence is insufficient to suspect a stall.
|
|
175
|
+
- `SUSPECTED_STALL`: multiple independent available signals show sustained inactivity.
|
|
176
|
+
- `FAILED`: the wrapped root command failed to launch or exited nonzero.
|
|
177
|
+
- `COMPLETE`: the wrapped root command completed successfully under current P0 semantics.
|
|
178
|
+
|
|
179
|
+
Only `FAILED` and `COMPLETE` are ordinary terminal success/failure states. `SUSPECTED_STALL`
|
|
180
|
+
can recover if activity resumes.
|
|
181
|
+
|
|
182
|
+
## P0 limitations
|
|
183
|
+
|
|
184
|
+
RunSentry P0 is intentionally small:
|
|
185
|
+
|
|
186
|
+
- local machine only;
|
|
187
|
+
- macOS and Linux target;
|
|
188
|
+
- no Windows compatibility promise yet;
|
|
189
|
+
- no web dashboard;
|
|
190
|
+
- no SaaS or remote monitoring;
|
|
191
|
+
- no notifications;
|
|
192
|
+
- no database;
|
|
193
|
+
- no workflow orchestration;
|
|
194
|
+
- no AI/LLM health judgment;
|
|
195
|
+
- no automatic kill or recovery;
|
|
196
|
+
- no OOM prediction;
|
|
197
|
+
- no disk exhaustion prediction;
|
|
198
|
+
- no generic job-completion ETA.
|
|
199
|
+
|
|
200
|
+
Process/resource visibility may be partial due to OS permissions. The health logic is
|
|
201
|
+
conservative and may miss real stalls rather than creating aggressive false positives.
|
|
202
|
+
|
|
203
|
+
## More detail
|
|
204
|
+
|
|
205
|
+
See [docs/public-alpha.md](docs/public-alpha.md) for the public-alpha readiness notes.
|
|
206
|
+
|
|
207
|
+
Historical design and implementation records live in `docs/RS-P0-*.md`.
|
|
208
|
+
|
|
209
|
+
## Reporting bugs
|
|
210
|
+
|
|
211
|
+
Use the GitHub issue templates. For behavior bugs, include:
|
|
212
|
+
|
|
213
|
+
- OS and Python version;
|
|
214
|
+
- the `runsentry run ...` command, with secrets removed;
|
|
215
|
+
- expected versus actual behavior;
|
|
216
|
+
- a sanitized `summary.json` excerpt if useful.
|
|
217
|
+
|
|
218
|
+
Do not share command arguments or paths that contain secrets.
|
|
219
|
+
|
|
220
|
+
## Feedback wanted
|
|
221
|
+
|
|
222
|
+
If you try RunSentry on a real local command, feedback on install friction, health-state
|
|
223
|
+
accuracy, `summary.json` usefulness, and missing P0 facts is especially useful.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# RunSentry
|
|
2
|
+
|
|
3
|
+
A lightweight local observer for long-running commands.
|
|
4
|
+
|
|
5
|
+
Wrap any command without modifying it:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
runsentry run --name training -- python train.py
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
RunSentry records:
|
|
12
|
+
|
|
13
|
+
- process/resource activity;
|
|
14
|
+
- stdout/stderr activity;
|
|
15
|
+
- watched file/directory changes;
|
|
16
|
+
- conservative health state;
|
|
17
|
+
- local JSONL telemetry;
|
|
18
|
+
- final `summary.json`.
|
|
19
|
+
|
|
20
|
+
No daemon.
|
|
21
|
+
No cloud.
|
|
22
|
+
No automatic killing.
|
|
23
|
+
|
|
24
|
+
It is for developers, researchers, and local AI/agent users who want factual evidence
|
|
25
|
+
about commands that may stay active for minutes or hours.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
runsentry run \
|
|
29
|
+
--name demo \
|
|
30
|
+
--watch /tmp/runsentry-demo-output.txt \
|
|
31
|
+
-- python examples/demo_long_job.py
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Public alpha status
|
|
35
|
+
|
|
36
|
+
RunSentry is in conservative public alpha. The P0 scope is useful for local observation
|
|
37
|
+
and debugging, but it is not a production supervisor or a guaranteed stuck-process
|
|
38
|
+
detector.
|
|
39
|
+
|
|
40
|
+
## Current P0 capabilities
|
|
41
|
+
|
|
42
|
+
RunSentry P0 can:
|
|
43
|
+
|
|
44
|
+
- launch a command directly with `shell=False`;
|
|
45
|
+
- preserve the child command argv after an explicit `--` boundary;
|
|
46
|
+
- tee child stdout and stderr back to the terminal as byte streams;
|
|
47
|
+
- count stdout/stderr bytes and chunks without storing output content;
|
|
48
|
+
- observe process/resource facts with `psutil`;
|
|
49
|
+
- observe watched file/directory size, mtime, file count, and disk usage facts;
|
|
50
|
+
- write local `.runsentry/runs/<run_id>/telemetry.jsonl`;
|
|
51
|
+
- write local `.runsentry/runs/<run_id>/summary.json`;
|
|
52
|
+
- emit conservative health states:
|
|
53
|
+
- `STARTING`
|
|
54
|
+
- `HEALTHY`
|
|
55
|
+
- `QUIET`
|
|
56
|
+
- `SUSPECTED_STALL`
|
|
57
|
+
- `FAILED`
|
|
58
|
+
- `COMPLETE`
|
|
59
|
+
|
|
60
|
+
`SUSPECTED_STALL` is a conservative suspicion, not proof. Silence alone is not a stall.
|
|
61
|
+
No watch path is not stall evidence. Unavailable metrics reduce confidence.
|
|
62
|
+
|
|
63
|
+
## Install from source
|
|
64
|
+
|
|
65
|
+
RunSentry currently targets Python 3.10+ on macOS and Linux.
|
|
66
|
+
|
|
67
|
+
Current source installation:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python -m pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For development tests:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m pip install -e ".[test]"
|
|
77
|
+
python -m pytest -q
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Future PyPI installation is planned, but the package has not been published to PyPI yet.
|
|
81
|
+
Until that happens, source installation is required.
|
|
82
|
+
|
|
83
|
+
## Basic usage
|
|
84
|
+
|
|
85
|
+
Always put the command to run after the explicit `--` boundary:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
runsentry run --name demo -- python3 -c "import time; time.sleep(2)"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Watch an output file or directory:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
runsentry run --name io-demo --watch out.log -- python3 job.py
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use more than one watch path:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
runsentry run --watch out.log --watch output_dir -- python3 job.py
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Everything after `--` belongs to the child command unchanged:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
runsentry run --name outer -- python3 job.py --watch child-value
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
RunSentry does not invoke a shell. If you need shell syntax, launch the shell explicitly:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
runsentry run --name shell-demo -- bash -lc 'python3 job.py | tee output.log'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
In that case, the shell is the observed root process and shell quoting rules are your
|
|
116
|
+
responsibility.
|
|
117
|
+
|
|
118
|
+
## Output artifacts
|
|
119
|
+
|
|
120
|
+
By default, each run writes:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
.runsentry/
|
|
124
|
+
runs/
|
|
125
|
+
<run_id>/
|
|
126
|
+
telemetry.jsonl
|
|
127
|
+
summary.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use `--output-dir` to place artifacts elsewhere:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
runsentry run --output-dir /tmp/runsentry-demo -- python3 job.py
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Telemetry is local. RunSentry does not upload data or contact a service.
|
|
137
|
+
|
|
138
|
+
RunSentry stores stdout/stderr activity counters, not stdout/stderr content. Command argv
|
|
139
|
+
is stored because it is part of the factual launch record, so avoid putting secrets in
|
|
140
|
+
command arguments.
|
|
141
|
+
|
|
142
|
+
Generated `.runsentry/` artifacts are ignored by this repository and should normally
|
|
143
|
+
stay out of commits.
|
|
144
|
+
|
|
145
|
+
## Health states
|
|
146
|
+
|
|
147
|
+
- `STARTING`: the run is inside the initial observation window.
|
|
148
|
+
- `HEALTHY`: recent positive activity was observed.
|
|
149
|
+
- `QUIET`: little activity is visible, but evidence is insufficient to suspect a stall.
|
|
150
|
+
- `SUSPECTED_STALL`: multiple independent available signals show sustained inactivity.
|
|
151
|
+
- `FAILED`: the wrapped root command failed to launch or exited nonzero.
|
|
152
|
+
- `COMPLETE`: the wrapped root command completed successfully under current P0 semantics.
|
|
153
|
+
|
|
154
|
+
Only `FAILED` and `COMPLETE` are ordinary terminal success/failure states. `SUSPECTED_STALL`
|
|
155
|
+
can recover if activity resumes.
|
|
156
|
+
|
|
157
|
+
## P0 limitations
|
|
158
|
+
|
|
159
|
+
RunSentry P0 is intentionally small:
|
|
160
|
+
|
|
161
|
+
- local machine only;
|
|
162
|
+
- macOS and Linux target;
|
|
163
|
+
- no Windows compatibility promise yet;
|
|
164
|
+
- no web dashboard;
|
|
165
|
+
- no SaaS or remote monitoring;
|
|
166
|
+
- no notifications;
|
|
167
|
+
- no database;
|
|
168
|
+
- no workflow orchestration;
|
|
169
|
+
- no AI/LLM health judgment;
|
|
170
|
+
- no automatic kill or recovery;
|
|
171
|
+
- no OOM prediction;
|
|
172
|
+
- no disk exhaustion prediction;
|
|
173
|
+
- no generic job-completion ETA.
|
|
174
|
+
|
|
175
|
+
Process/resource visibility may be partial due to OS permissions. The health logic is
|
|
176
|
+
conservative and may miss real stalls rather than creating aggressive false positives.
|
|
177
|
+
|
|
178
|
+
## More detail
|
|
179
|
+
|
|
180
|
+
See [docs/public-alpha.md](docs/public-alpha.md) for the public-alpha readiness notes.
|
|
181
|
+
|
|
182
|
+
Historical design and implementation records live in `docs/RS-P0-*.md`.
|
|
183
|
+
|
|
184
|
+
## Reporting bugs
|
|
185
|
+
|
|
186
|
+
Use the GitHub issue templates. For behavior bugs, include:
|
|
187
|
+
|
|
188
|
+
- OS and Python version;
|
|
189
|
+
- the `runsentry run ...` command, with secrets removed;
|
|
190
|
+
- expected versus actual behavior;
|
|
191
|
+
- a sanitized `summary.json` excerpt if useful.
|
|
192
|
+
|
|
193
|
+
Do not share command arguments or paths that contain secrets.
|
|
194
|
+
|
|
195
|
+
## Feedback wanted
|
|
196
|
+
|
|
197
|
+
If you try RunSentry on a real local command, feedback on install friction, health-state
|
|
198
|
+
accuracy, `summary.json` usefulness, and missing P0 facts is especially useful.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runsentry"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "A lightweight local health observer for long-running commands and Python jobs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "RunSentry contributors"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Operating System :: POSIX :: Linux",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: System :: Monitoring",
|
|
26
|
+
"Topic :: Utilities"
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"psutil>=5.9"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
test = [
|
|
34
|
+
"pytest>=7"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
runsentry = "runsentry.cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from .execution import EXIT_USAGE, LaunchError, RunSpec, run_command
|
|
8
|
+
from .observation import DEFAULT_RESOURCE_SAMPLE_INTERVAL_S
|
|
9
|
+
from .version import __version__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
prog="runsentry",
|
|
15
|
+
description="Local conservative health observer for one long-running command.",
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--version",
|
|
19
|
+
action="version",
|
|
20
|
+
version=f"%(prog)s {__version__}",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
subparsers = parser.add_subparsers(dest="command_name")
|
|
24
|
+
run_parser = subparsers.add_parser(
|
|
25
|
+
"run",
|
|
26
|
+
help="observe a command after an explicit -- boundary",
|
|
27
|
+
description=(
|
|
28
|
+
"Launch COMMAND directly with shell=False, tee stdout/stderr, "
|
|
29
|
+
"observe local facts, and write .runsentry telemetry."
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
run_parser.add_argument("--name", default=None, help="optional human-readable run name")
|
|
33
|
+
run_parser.add_argument(
|
|
34
|
+
"--interval",
|
|
35
|
+
type=float,
|
|
36
|
+
default=DEFAULT_RESOURCE_SAMPLE_INTERVAL_S,
|
|
37
|
+
help="resource/watch/telemetry sampling interval in seconds",
|
|
38
|
+
)
|
|
39
|
+
run_parser.add_argument(
|
|
40
|
+
"--watch",
|
|
41
|
+
action="append",
|
|
42
|
+
default=[],
|
|
43
|
+
help="path to observe for factual size/mtime/disk data",
|
|
44
|
+
)
|
|
45
|
+
run_parser.add_argument(
|
|
46
|
+
"--output-dir",
|
|
47
|
+
default=None,
|
|
48
|
+
help="directory for RunSentry telemetry artifacts",
|
|
49
|
+
)
|
|
50
|
+
run_parser.add_argument(
|
|
51
|
+
"run_args",
|
|
52
|
+
nargs=argparse.REMAINDER,
|
|
53
|
+
help="must contain -- followed by COMMAND [ARG ...]; all arguments after -- belong to the child",
|
|
54
|
+
)
|
|
55
|
+
run_parser.set_defaults(func=_run)
|
|
56
|
+
return parser
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _parse_run_args(
|
|
60
|
+
name: str | None,
|
|
61
|
+
interval: float,
|
|
62
|
+
watches: Sequence[str],
|
|
63
|
+
output_dir: str | None,
|
|
64
|
+
run_args: Sequence[str],
|
|
65
|
+
) -> RunSpec:
|
|
66
|
+
if interval < 1.0 or interval > 60.0:
|
|
67
|
+
raise LaunchError("runsentry run --interval must be between 1.0 and 60.0.", EXIT_USAGE)
|
|
68
|
+
|
|
69
|
+
if "--" not in run_args:
|
|
70
|
+
raise LaunchError(
|
|
71
|
+
"runsentry run requires an explicit -- before the command.",
|
|
72
|
+
EXIT_USAGE,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
boundary_index = list(run_args).index("--")
|
|
76
|
+
option_args = list(run_args[:boundary_index])
|
|
77
|
+
command_argv = list(run_args[boundary_index + 1 :])
|
|
78
|
+
if not command_argv:
|
|
79
|
+
raise LaunchError("runsentry run requires a command after --.", EXIT_USAGE)
|
|
80
|
+
|
|
81
|
+
watch_paths = list(watches) + _parse_watch_options(option_args)
|
|
82
|
+
|
|
83
|
+
return RunSpec(
|
|
84
|
+
name=name,
|
|
85
|
+
argv=command_argv,
|
|
86
|
+
sample_interval_s=interval,
|
|
87
|
+
watch_paths=watch_paths,
|
|
88
|
+
output_dir=output_dir,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _parse_watch_options(option_args: list[str]) -> list[str]:
|
|
93
|
+
watch_paths: list[str] = []
|
|
94
|
+
index = 0
|
|
95
|
+
while index < len(option_args):
|
|
96
|
+
arg = option_args[index]
|
|
97
|
+
if arg != "--watch":
|
|
98
|
+
raise LaunchError(f"unexpected argument before --: {arg}", EXIT_USAGE)
|
|
99
|
+
index += 1
|
|
100
|
+
if index >= len(option_args):
|
|
101
|
+
raise LaunchError("runsentry run --watch requires a path.", EXIT_USAGE)
|
|
102
|
+
watch_paths.append(option_args[index])
|
|
103
|
+
index += 1
|
|
104
|
+
return watch_paths
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _run(args: argparse.Namespace) -> int:
|
|
108
|
+
try:
|
|
109
|
+
run_spec = _parse_run_args(
|
|
110
|
+
args.name,
|
|
111
|
+
args.interval,
|
|
112
|
+
args.watch,
|
|
113
|
+
args.output_dir,
|
|
114
|
+
args.run_args,
|
|
115
|
+
)
|
|
116
|
+
return run_command(run_spec)
|
|
117
|
+
except LaunchError as exc:
|
|
118
|
+
print(f"runsentry: {exc}", file=sys.stderr)
|
|
119
|
+
return exc.exit_code
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
123
|
+
parser = build_parser()
|
|
124
|
+
args = parser.parse_args(argv)
|
|
125
|
+
if not hasattr(args, "func"):
|
|
126
|
+
parser.print_help()
|
|
127
|
+
return 0
|
|
128
|
+
return int(args.func(args))
|