pcpulse 0.1.0__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.
- pcpulse-0.1.0/.github/workflows/ci.yml +22 -0
- pcpulse-0.1.0/.gitignore +10 -0
- pcpulse-0.1.0/CHANGELOG.md +10 -0
- pcpulse-0.1.0/CODE_OF_CONDUCT.md +5 -0
- pcpulse-0.1.0/CONTRIBUTING.md +17 -0
- pcpulse-0.1.0/GOVERNANCE.md +5 -0
- pcpulse-0.1.0/LICENSE +21 -0
- pcpulse-0.1.0/MAINTAINERS.md +5 -0
- pcpulse-0.1.0/PKG-INFO +255 -0
- pcpulse-0.1.0/README.md +215 -0
- pcpulse-0.1.0/SECURITY.md +7 -0
- pcpulse-0.1.0/SUPPORT.md +7 -0
- pcpulse-0.1.0/docs/ARCHITECTURE.md +32 -0
- pcpulse-0.1.0/docs/DESIGN_PRINCIPLES.md +29 -0
- pcpulse-0.1.0/examples/library_usage.py +7 -0
- pcpulse-0.1.0/pcpulse.zip +0 -0
- pcpulse-0.1.0/pyproject.toml +34 -0
- pcpulse-0.1.0/schemas/snapshot-1.0.schema.json +31 -0
- pcpulse-0.1.0/src/pcpulse/__init__.py +6 -0
- pcpulse-0.1.0/src/pcpulse/cli.py +67 -0
- pcpulse-0.1.0/src/pcpulse/collectors.py +25 -0
- pcpulse-0.1.0/src/pcpulse/health.py +16 -0
- pcpulse-0.1.0/src/pcpulse/models.py +41 -0
- pcpulse-0.1.0/src/pcpulse/py.typed +0 -0
- pcpulse-0.1.0/src/pcpulse/serialization.py +12 -0
- pcpulse-0.1.0/tests/test_cli.py +9 -0
- pcpulse-0.1.0/tests/test_collectors.py +5 -0
- pcpulse-0.1.0/tests/test_health.py +5 -0
- pcpulse-0.1.0/tests/test_serialization.py +7 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python-version }}
|
|
18
|
+
- run: python -m pip install --upgrade pip
|
|
19
|
+
- run: pip install -e '.[dev]'
|
|
20
|
+
- run: pytest -q
|
|
21
|
+
- run: ruff check .
|
|
22
|
+
- run: mypy src
|
pcpulse-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — Initial public release
|
|
4
|
+
|
|
5
|
+
- Cross-platform system snapshot API
|
|
6
|
+
- JSON and Markdown serialization
|
|
7
|
+
- CLI snapshot and doctor commands
|
|
8
|
+
- Versioned snapshot schema
|
|
9
|
+
- Privacy-conscious identity opt-in
|
|
10
|
+
- Automated tests and development tooling
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
PCPulse is committed to a respectful, technical, and inclusive community. Participants must avoid harassment, personal attacks, discrimination, doxxing, and intentionally disruptive behavior.
|
|
4
|
+
|
|
5
|
+
Maintainers may moderate discussions, reject contributions, or restrict participation when behavior violates these expectations.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions are welcome. Keep changes focused and include tests for behavior changes.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
python -m pip install -e '.[dev]'
|
|
8
|
+
|
|
9
|
+
Run:
|
|
10
|
+
|
|
11
|
+
pytest -q
|
|
12
|
+
ruff check .
|
|
13
|
+
mypy src
|
|
14
|
+
|
|
15
|
+
## Pull requests
|
|
16
|
+
|
|
17
|
+
Explain the problem, approach, testing performed, and compatibility or privacy implications.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Governance
|
|
2
|
+
|
|
3
|
+
PCPulse uses a maintainer-led model. Technical decisions should be discussed openly in issues and pull requests whenever practical.
|
|
4
|
+
|
|
5
|
+
Changes affecting public APIs, schemas, privacy behavior, security, or compatibility require additional review and documentation.
|
pcpulse-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Para
|
|
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.
|
pcpulse-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pcpulse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Privacy-first cross-platform PC diagnostics library and CLI.
|
|
5
|
+
Author: Matthew Para
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Matthew Para
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Keywords: cli,diagnostics,hardware,pc,privacy,system
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Requires-Python: >=3.10
|
|
33
|
+
Requires-Dist: psutil<8,>=5.9
|
|
34
|
+
Requires-Dist: rich<15,>=13
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
38
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# PCPulse
|
|
42
|
+
|
|
43
|
+
**Local-first, privacy-conscious, cross-platform PC diagnostics for humans, scripts, support tools, and developer applications.**
|
|
44
|
+
|
|
45
|
+
PCPulse is an open-source Python library and CLI for collecting useful system diagnostics without requiring a cloud service. It turns local machine information into typed Python objects and stable machine-readable output.
|
|
46
|
+
|
|
47
|
+
## Highlights
|
|
48
|
+
|
|
49
|
+
- Cross-platform system snapshots
|
|
50
|
+
- CPU, memory, disk, OS, architecture, and uptime information
|
|
51
|
+
- JSON and Markdown output
|
|
52
|
+
- Conservative local health checks with `pcpulse doctor`
|
|
53
|
+
- Versioned snapshot schema
|
|
54
|
+
- Privacy-first identity handling
|
|
55
|
+
- Python library API for integration into other tools
|
|
56
|
+
- No telemetry or mandatory network service
|
|
57
|
+
- MIT licensed
|
|
58
|
+
- Tests, linting, type checking, CI, security guidance, and contributor documentation
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
Install the published package when available:
|
|
63
|
+
|
|
64
|
+
python -m pip install pcpulse
|
|
65
|
+
|
|
66
|
+
For development:
|
|
67
|
+
|
|
68
|
+
git clone https://github.com/matthewpara567/pcpulse.git
|
|
69
|
+
cd pcpulse
|
|
70
|
+
python -m pip install -e '.[dev]'
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
Collect a human-readable snapshot:
|
|
75
|
+
|
|
76
|
+
pcpulse snapshot
|
|
77
|
+
|
|
78
|
+
Get JSON:
|
|
79
|
+
|
|
80
|
+
pcpulse snapshot --json
|
|
81
|
+
|
|
82
|
+
Get Markdown:
|
|
83
|
+
|
|
84
|
+
pcpulse snapshot --markdown
|
|
85
|
+
|
|
86
|
+
Run health checks:
|
|
87
|
+
|
|
88
|
+
pcpulse doctor
|
|
89
|
+
|
|
90
|
+
Get machine-readable health checks:
|
|
91
|
+
|
|
92
|
+
pcpulse doctor --json
|
|
93
|
+
|
|
94
|
+
Identity information is excluded by default. Opt in explicitly:
|
|
95
|
+
|
|
96
|
+
pcpulse snapshot --include-identity
|
|
97
|
+
|
|
98
|
+
## Python API
|
|
99
|
+
|
|
100
|
+
from pcpulse import collect_snapshot, run_checks
|
|
101
|
+
|
|
102
|
+
snapshot = collect_snapshot()
|
|
103
|
+
print(snapshot.to_dict())
|
|
104
|
+
|
|
105
|
+
for check in run_checks(snapshot):
|
|
106
|
+
print(check.status, check.message)
|
|
107
|
+
|
|
108
|
+
The library is intended to be the stable integration layer. The CLI is an interface over that library.
|
|
109
|
+
|
|
110
|
+
## Privacy
|
|
111
|
+
|
|
112
|
+
PCPulse is designed around a local-first model:
|
|
113
|
+
|
|
114
|
+
- No telemetry is required.
|
|
115
|
+
- No account is required.
|
|
116
|
+
- No cloud backend is required.
|
|
117
|
+
- Hostname and username are not collected unless explicitly requested.
|
|
118
|
+
- Collectors are read-only diagnostics and do not intentionally modify the host.
|
|
119
|
+
- Applications should sanitize reports before sharing them externally.
|
|
120
|
+
|
|
121
|
+
A diagnostic report can still contain machine-specific information such as operating-system details, mount paths, hardware counts, or uptime. Review output before publishing it.
|
|
122
|
+
|
|
123
|
+
## Snapshot schema
|
|
124
|
+
|
|
125
|
+
Snapshots carry an explicit schema version so consumers can distinguish format changes:
|
|
126
|
+
|
|
127
|
+
"schema_version": "1.0"
|
|
128
|
+
|
|
129
|
+
The canonical JSON Schema is stored at:
|
|
130
|
+
|
|
131
|
+
schemas/snapshot-1.0.schema.json
|
|
132
|
+
|
|
133
|
+
Consumers should treat schema changes as compatibility-sensitive and avoid assuming that every platform exposes every metric.
|
|
134
|
+
|
|
135
|
+
## Health checks
|
|
136
|
+
|
|
137
|
+
`pcpulse doctor` performs conservative checks for conditions such as high memory usage and high disk utilization.
|
|
138
|
+
|
|
139
|
+
A check can report:
|
|
140
|
+
|
|
141
|
+
- `ok`
|
|
142
|
+
- `warning`
|
|
143
|
+
- `error`
|
|
144
|
+
- `unknown`
|
|
145
|
+
|
|
146
|
+
Unknown is preferred over inventing a value when the operating system does not expose a metric.
|
|
147
|
+
|
|
148
|
+
## Architecture
|
|
149
|
+
|
|
150
|
+
PCPulse separates:
|
|
151
|
+
|
|
152
|
+
1. **Collectors** — read local system information.
|
|
153
|
+
2. **Models** — provide typed Python representations.
|
|
154
|
+
3. **Serialization** — converts snapshots to JSON or Markdown.
|
|
155
|
+
4. **Health checks** — interprets selected metrics.
|
|
156
|
+
5. **CLI** — provides a user-facing command interface.
|
|
157
|
+
|
|
158
|
+
This separation makes PCPulse useful as a library without requiring applications to scrape terminal output.
|
|
159
|
+
|
|
160
|
+
## Development
|
|
161
|
+
|
|
162
|
+
Install development dependencies:
|
|
163
|
+
|
|
164
|
+
python -m pip install -e '.[dev]'
|
|
165
|
+
|
|
166
|
+
Run the test suite:
|
|
167
|
+
|
|
168
|
+
pytest -q
|
|
169
|
+
|
|
170
|
+
Run linting:
|
|
171
|
+
|
|
172
|
+
ruff check .
|
|
173
|
+
|
|
174
|
+
Run type checking:
|
|
175
|
+
|
|
176
|
+
mypy src
|
|
177
|
+
|
|
178
|
+
## Contributing
|
|
179
|
+
|
|
180
|
+
Bug fixes, documentation improvements, tests, platform compatibility work, and carefully scoped features are welcome.
|
|
181
|
+
|
|
182
|
+
Before opening a pull request:
|
|
183
|
+
|
|
184
|
+
- Keep the change focused.
|
|
185
|
+
- Add or update tests where appropriate.
|
|
186
|
+
- Document public API or schema changes.
|
|
187
|
+
- Consider privacy implications.
|
|
188
|
+
- Run the test, lint, and type-check commands.
|
|
189
|
+
- Avoid unrelated formatting churn.
|
|
190
|
+
|
|
191
|
+
See CONTRIBUTING.md for details.
|
|
192
|
+
|
|
193
|
+
## Security
|
|
194
|
+
|
|
195
|
+
Please do not disclose vulnerabilities in public issues. Follow SECURITY.md for responsible reporting.
|
|
196
|
+
|
|
197
|
+
Security-sensitive areas include unintended network activity, unsafe parsing, dependency vulnerabilities, command execution, and accidental disclosure of local information.
|
|
198
|
+
|
|
199
|
+
## Project structure
|
|
200
|
+
|
|
201
|
+
pcpulse/
|
|
202
|
+
├── .github/
|
|
203
|
+
│ ├── ISSUE_TEMPLATE/
|
|
204
|
+
│ └── workflows/
|
|
205
|
+
├── docs/
|
|
206
|
+
├── examples/
|
|
207
|
+
├── schemas/
|
|
208
|
+
├── src/
|
|
209
|
+
│ └── pcpulse/
|
|
210
|
+
├── tests/
|
|
211
|
+
├── CHANGELOG.md
|
|
212
|
+
├── CODE_OF_CONDUCT.md
|
|
213
|
+
├── CONTRIBUTING.md
|
|
214
|
+
├── GOVERNANCE.md
|
|
215
|
+
├── LICENSE
|
|
216
|
+
├── MAINTAINERS.md
|
|
217
|
+
├── README.md
|
|
218
|
+
├── RELEASING.md
|
|
219
|
+
├── SECURITY.md
|
|
220
|
+
├── SUPPORT.md
|
|
221
|
+
└── pyproject.toml
|
|
222
|
+
|
|
223
|
+
## Design principles
|
|
224
|
+
|
|
225
|
+
PCPulse follows a few simple principles:
|
|
226
|
+
|
|
227
|
+
- **Local-first:** the core collector should work without a cloud service.
|
|
228
|
+
- **Privacy-first:** identity data is opt-in.
|
|
229
|
+
- **Structured:** machine-readable output is a first-class interface.
|
|
230
|
+
- **Best-effort:** unavailable metrics are represented as unavailable.
|
|
231
|
+
- **Read-only:** diagnostics should not silently modify the host.
|
|
232
|
+
- **Compatibility-conscious:** public API and schema changes should be deliberate.
|
|
233
|
+
- **Small core:** features should justify their maintenance cost.
|
|
234
|
+
|
|
235
|
+
## Roadmap
|
|
236
|
+
|
|
237
|
+
Potential future work includes:
|
|
238
|
+
|
|
239
|
+
- More detailed platform-specific hardware metrics
|
|
240
|
+
- Battery, temperature, and fan information where safely available
|
|
241
|
+
- Pluggable collectors
|
|
242
|
+
- Richer diagnostic bundles
|
|
243
|
+
- More schema validation tooling
|
|
244
|
+
- Additional operating-system compatibility tests
|
|
245
|
+
- Release provenance and artifact-integrity improvements
|
|
246
|
+
|
|
247
|
+
Roadmap items are not promises and may change as the project evolves.
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
PCPulse is released under the MIT License. See LICENSE.
|
|
252
|
+
|
|
253
|
+
## Status
|
|
254
|
+
|
|
255
|
+
PCPulse is currently an early open-source project. APIs and schema details may evolve before the first stable release.
|
pcpulse-0.1.0/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# PCPulse
|
|
2
|
+
|
|
3
|
+
**Local-first, privacy-conscious, cross-platform PC diagnostics for humans, scripts, support tools, and developer applications.**
|
|
4
|
+
|
|
5
|
+
PCPulse is an open-source Python library and CLI for collecting useful system diagnostics without requiring a cloud service. It turns local machine information into typed Python objects and stable machine-readable output.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
- Cross-platform system snapshots
|
|
10
|
+
- CPU, memory, disk, OS, architecture, and uptime information
|
|
11
|
+
- JSON and Markdown output
|
|
12
|
+
- Conservative local health checks with `pcpulse doctor`
|
|
13
|
+
- Versioned snapshot schema
|
|
14
|
+
- Privacy-first identity handling
|
|
15
|
+
- Python library API for integration into other tools
|
|
16
|
+
- No telemetry or mandatory network service
|
|
17
|
+
- MIT licensed
|
|
18
|
+
- Tests, linting, type checking, CI, security guidance, and contributor documentation
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install the published package when available:
|
|
23
|
+
|
|
24
|
+
python -m pip install pcpulse
|
|
25
|
+
|
|
26
|
+
For development:
|
|
27
|
+
|
|
28
|
+
git clone https://github.com/matthewpara567/pcpulse.git
|
|
29
|
+
cd pcpulse
|
|
30
|
+
python -m pip install -e '.[dev]'
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
Collect a human-readable snapshot:
|
|
35
|
+
|
|
36
|
+
pcpulse snapshot
|
|
37
|
+
|
|
38
|
+
Get JSON:
|
|
39
|
+
|
|
40
|
+
pcpulse snapshot --json
|
|
41
|
+
|
|
42
|
+
Get Markdown:
|
|
43
|
+
|
|
44
|
+
pcpulse snapshot --markdown
|
|
45
|
+
|
|
46
|
+
Run health checks:
|
|
47
|
+
|
|
48
|
+
pcpulse doctor
|
|
49
|
+
|
|
50
|
+
Get machine-readable health checks:
|
|
51
|
+
|
|
52
|
+
pcpulse doctor --json
|
|
53
|
+
|
|
54
|
+
Identity information is excluded by default. Opt in explicitly:
|
|
55
|
+
|
|
56
|
+
pcpulse snapshot --include-identity
|
|
57
|
+
|
|
58
|
+
## Python API
|
|
59
|
+
|
|
60
|
+
from pcpulse import collect_snapshot, run_checks
|
|
61
|
+
|
|
62
|
+
snapshot = collect_snapshot()
|
|
63
|
+
print(snapshot.to_dict())
|
|
64
|
+
|
|
65
|
+
for check in run_checks(snapshot):
|
|
66
|
+
print(check.status, check.message)
|
|
67
|
+
|
|
68
|
+
The library is intended to be the stable integration layer. The CLI is an interface over that library.
|
|
69
|
+
|
|
70
|
+
## Privacy
|
|
71
|
+
|
|
72
|
+
PCPulse is designed around a local-first model:
|
|
73
|
+
|
|
74
|
+
- No telemetry is required.
|
|
75
|
+
- No account is required.
|
|
76
|
+
- No cloud backend is required.
|
|
77
|
+
- Hostname and username are not collected unless explicitly requested.
|
|
78
|
+
- Collectors are read-only diagnostics and do not intentionally modify the host.
|
|
79
|
+
- Applications should sanitize reports before sharing them externally.
|
|
80
|
+
|
|
81
|
+
A diagnostic report can still contain machine-specific information such as operating-system details, mount paths, hardware counts, or uptime. Review output before publishing it.
|
|
82
|
+
|
|
83
|
+
## Snapshot schema
|
|
84
|
+
|
|
85
|
+
Snapshots carry an explicit schema version so consumers can distinguish format changes:
|
|
86
|
+
|
|
87
|
+
"schema_version": "1.0"
|
|
88
|
+
|
|
89
|
+
The canonical JSON Schema is stored at:
|
|
90
|
+
|
|
91
|
+
schemas/snapshot-1.0.schema.json
|
|
92
|
+
|
|
93
|
+
Consumers should treat schema changes as compatibility-sensitive and avoid assuming that every platform exposes every metric.
|
|
94
|
+
|
|
95
|
+
## Health checks
|
|
96
|
+
|
|
97
|
+
`pcpulse doctor` performs conservative checks for conditions such as high memory usage and high disk utilization.
|
|
98
|
+
|
|
99
|
+
A check can report:
|
|
100
|
+
|
|
101
|
+
- `ok`
|
|
102
|
+
- `warning`
|
|
103
|
+
- `error`
|
|
104
|
+
- `unknown`
|
|
105
|
+
|
|
106
|
+
Unknown is preferred over inventing a value when the operating system does not expose a metric.
|
|
107
|
+
|
|
108
|
+
## Architecture
|
|
109
|
+
|
|
110
|
+
PCPulse separates:
|
|
111
|
+
|
|
112
|
+
1. **Collectors** — read local system information.
|
|
113
|
+
2. **Models** — provide typed Python representations.
|
|
114
|
+
3. **Serialization** — converts snapshots to JSON or Markdown.
|
|
115
|
+
4. **Health checks** — interprets selected metrics.
|
|
116
|
+
5. **CLI** — provides a user-facing command interface.
|
|
117
|
+
|
|
118
|
+
This separation makes PCPulse useful as a library without requiring applications to scrape terminal output.
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
Install development dependencies:
|
|
123
|
+
|
|
124
|
+
python -m pip install -e '.[dev]'
|
|
125
|
+
|
|
126
|
+
Run the test suite:
|
|
127
|
+
|
|
128
|
+
pytest -q
|
|
129
|
+
|
|
130
|
+
Run linting:
|
|
131
|
+
|
|
132
|
+
ruff check .
|
|
133
|
+
|
|
134
|
+
Run type checking:
|
|
135
|
+
|
|
136
|
+
mypy src
|
|
137
|
+
|
|
138
|
+
## Contributing
|
|
139
|
+
|
|
140
|
+
Bug fixes, documentation improvements, tests, platform compatibility work, and carefully scoped features are welcome.
|
|
141
|
+
|
|
142
|
+
Before opening a pull request:
|
|
143
|
+
|
|
144
|
+
- Keep the change focused.
|
|
145
|
+
- Add or update tests where appropriate.
|
|
146
|
+
- Document public API or schema changes.
|
|
147
|
+
- Consider privacy implications.
|
|
148
|
+
- Run the test, lint, and type-check commands.
|
|
149
|
+
- Avoid unrelated formatting churn.
|
|
150
|
+
|
|
151
|
+
See CONTRIBUTING.md for details.
|
|
152
|
+
|
|
153
|
+
## Security
|
|
154
|
+
|
|
155
|
+
Please do not disclose vulnerabilities in public issues. Follow SECURITY.md for responsible reporting.
|
|
156
|
+
|
|
157
|
+
Security-sensitive areas include unintended network activity, unsafe parsing, dependency vulnerabilities, command execution, and accidental disclosure of local information.
|
|
158
|
+
|
|
159
|
+
## Project structure
|
|
160
|
+
|
|
161
|
+
pcpulse/
|
|
162
|
+
├── .github/
|
|
163
|
+
│ ├── ISSUE_TEMPLATE/
|
|
164
|
+
│ └── workflows/
|
|
165
|
+
├── docs/
|
|
166
|
+
├── examples/
|
|
167
|
+
├── schemas/
|
|
168
|
+
├── src/
|
|
169
|
+
│ └── pcpulse/
|
|
170
|
+
├── tests/
|
|
171
|
+
├── CHANGELOG.md
|
|
172
|
+
├── CODE_OF_CONDUCT.md
|
|
173
|
+
├── CONTRIBUTING.md
|
|
174
|
+
├── GOVERNANCE.md
|
|
175
|
+
├── LICENSE
|
|
176
|
+
├── MAINTAINERS.md
|
|
177
|
+
├── README.md
|
|
178
|
+
├── RELEASING.md
|
|
179
|
+
├── SECURITY.md
|
|
180
|
+
├── SUPPORT.md
|
|
181
|
+
└── pyproject.toml
|
|
182
|
+
|
|
183
|
+
## Design principles
|
|
184
|
+
|
|
185
|
+
PCPulse follows a few simple principles:
|
|
186
|
+
|
|
187
|
+
- **Local-first:** the core collector should work without a cloud service.
|
|
188
|
+
- **Privacy-first:** identity data is opt-in.
|
|
189
|
+
- **Structured:** machine-readable output is a first-class interface.
|
|
190
|
+
- **Best-effort:** unavailable metrics are represented as unavailable.
|
|
191
|
+
- **Read-only:** diagnostics should not silently modify the host.
|
|
192
|
+
- **Compatibility-conscious:** public API and schema changes should be deliberate.
|
|
193
|
+
- **Small core:** features should justify their maintenance cost.
|
|
194
|
+
|
|
195
|
+
## Roadmap
|
|
196
|
+
|
|
197
|
+
Potential future work includes:
|
|
198
|
+
|
|
199
|
+
- More detailed platform-specific hardware metrics
|
|
200
|
+
- Battery, temperature, and fan information where safely available
|
|
201
|
+
- Pluggable collectors
|
|
202
|
+
- Richer diagnostic bundles
|
|
203
|
+
- More schema validation tooling
|
|
204
|
+
- Additional operating-system compatibility tests
|
|
205
|
+
- Release provenance and artifact-integrity improvements
|
|
206
|
+
|
|
207
|
+
Roadmap items are not promises and may change as the project evolves.
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
PCPulse is released under the MIT License. See LICENSE.
|
|
212
|
+
|
|
213
|
+
## Status
|
|
214
|
+
|
|
215
|
+
PCPulse is currently an early open-source project. APIs and schema details may evolve before the first stable release.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
Do not disclose security vulnerabilities in public issues. Report suspected vulnerabilities privately through GitHub's private vulnerability reporting when available.
|
|
4
|
+
|
|
5
|
+
Do not include passwords, API keys, private keys, tokens, or personal data in reports.
|
|
6
|
+
|
|
7
|
+
PCPulse is designed as a local read-only diagnostics library. Reports involving unintended network access, data disclosure, command execution, unsafe parsing, or dependency vulnerabilities are especially important.
|
pcpulse-0.1.0/SUPPORT.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Support
|
|
2
|
+
|
|
3
|
+
For bugs, open a GitHub issue with the Python version, operating system, PCPulse version, reproduction steps, and sanitized output.
|
|
4
|
+
|
|
5
|
+
Do not post credentials, private tokens, private keys, or other sensitive information.
|
|
6
|
+
|
|
7
|
+
For security issues, follow SECURITY.md.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
PCPulse separates collection, models, serialization, health checks, and the CLI.
|
|
4
|
+
|
|
5
|
+
## Data flow
|
|
6
|
+
|
|
7
|
+
Operating system
|
|
8
|
+
|
|
|
9
|
+
v
|
|
10
|
+
Collectors
|
|
11
|
+
|
|
|
12
|
+
v
|
|
13
|
+
Models
|
|
14
|
+
/ \
|
|
15
|
+
v v
|
|
16
|
+
Serialization Health checks
|
|
17
|
+
| |
|
|
18
|
+
v v
|
|
19
|
+
JSON/MD Doctor
|
|
20
|
+
\
|
|
21
|
+
v
|
|
22
|
+
CLI
|
|
23
|
+
|
|
24
|
+
Collectors read local information and return typed models. Serialization converts those models to external formats. Health checks interpret collected values without modifying the machine. The CLI provides a user-facing interface over the library.
|
|
25
|
+
|
|
26
|
+
This separation allows downstream applications to import PCPulse instead of parsing terminal output.
|
|
27
|
+
|
|
28
|
+
## Compatibility
|
|
29
|
+
|
|
30
|
+
Platform-specific collectors should degrade gracefully. If a metric is unavailable, the model should represent it as unavailable rather than guessing.
|
|
31
|
+
|
|
32
|
+
Public schema changes should be documented and versioned deliberately.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Design Principles
|
|
2
|
+
|
|
3
|
+
## Local-first
|
|
4
|
+
|
|
5
|
+
The core functionality should work without a hosted service or mandatory network connection.
|
|
6
|
+
|
|
7
|
+
## Privacy-first
|
|
8
|
+
|
|
9
|
+
Identity information is opt-in. Diagnostic output should be treated as potentially sensitive even when PCPulse itself does not transmit it.
|
|
10
|
+
|
|
11
|
+
## Structured output
|
|
12
|
+
|
|
13
|
+
JSON and typed Python objects are first-class interfaces. Human-readable terminal output is useful, but applications should use the library or structured formats.
|
|
14
|
+
|
|
15
|
+
## Best-effort collection
|
|
16
|
+
|
|
17
|
+
Operating systems expose different metrics. PCPulse should report unavailable values rather than fabricate data.
|
|
18
|
+
|
|
19
|
+
## Read-only diagnostics
|
|
20
|
+
|
|
21
|
+
Collectors should observe the host rather than silently modifying it.
|
|
22
|
+
|
|
23
|
+
## Compatibility
|
|
24
|
+
|
|
25
|
+
Changes to public APIs, output schemas, and command behavior should be deliberate and documented.
|
|
26
|
+
|
|
27
|
+
## Maintainability
|
|
28
|
+
|
|
29
|
+
The project should prefer understandable implementations and a small dependency surface over unnecessary complexity.
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pcpulse"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Privacy-first cross-platform PC diagnostics library and CLI."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Matthew Para" }]
|
|
13
|
+
keywords = ["diagnostics", "system", "hardware", "pc", "cli", "privacy"]
|
|
14
|
+
classifiers = ["Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Operating System :: OS Independent"]
|
|
15
|
+
dependencies = ["psutil>=5.9,<8", "rich>=13,<15"]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11"]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
pcpulse = "pcpulse.cli:main"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["src/pcpulse"]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
testpaths = ["tests"]
|
|
28
|
+
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
line-length = 100
|
|
31
|
+
|
|
32
|
+
[tool.mypy]
|
|
33
|
+
python_version = "3.10"
|
|
34
|
+
strict = true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/matthewpara567/pcpulse/blob/main/schemas/snapshot-1.0.schema.json",
|
|
4
|
+
"title": "PCPulse Snapshot",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": [
|
|
7
|
+
"collected_at",
|
|
8
|
+
"schema_version",
|
|
9
|
+
"os",
|
|
10
|
+
"kernel",
|
|
11
|
+
"architecture",
|
|
12
|
+
"cpu",
|
|
13
|
+
"memory",
|
|
14
|
+
"disks",
|
|
15
|
+
"uptime_seconds"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"collected_at": { "type": "string" },
|
|
19
|
+
"schema_version": { "const": "1.0" },
|
|
20
|
+
"os": { "type": "string" },
|
|
21
|
+
"kernel": { "type": "string" },
|
|
22
|
+
"architecture": { "type": "string" },
|
|
23
|
+
"cpu": { "type": "object" },
|
|
24
|
+
"memory": { "type": "object" },
|
|
25
|
+
"disks": { "type": "array" },
|
|
26
|
+
"uptime_seconds": { "type": ["number", "null"] },
|
|
27
|
+
"hostname": { "type": ["string", "null"] },
|
|
28
|
+
"username": { "type": ["string", "null"] },
|
|
29
|
+
"extra": { "type": "object" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from . import __version__, collect_snapshot, run_checks
|
|
5
|
+
from .serialization import to_json, to_markdown
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
prog="pcpulse",
|
|
11
|
+
description="Privacy-first cross-platform PC diagnostics.",
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
14
|
+
|
|
15
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
16
|
+
|
|
17
|
+
snapshot = sub.add_parser("snapshot", help="Collect a local system snapshot.")
|
|
18
|
+
snapshot.add_argument("--json", action="store_true")
|
|
19
|
+
snapshot.add_argument("--markdown", action="store_true")
|
|
20
|
+
snapshot.add_argument("--include-identity", action="store_true")
|
|
21
|
+
|
|
22
|
+
doctor = sub.add_parser("doctor", help="Run conservative local health checks.")
|
|
23
|
+
doctor.add_argument("--json", action="store_true")
|
|
24
|
+
|
|
25
|
+
sub.add_parser("schema", help="Show the current snapshot schema version.")
|
|
26
|
+
return parser
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main() -> int:
|
|
30
|
+
args = build_parser().parse_args()
|
|
31
|
+
|
|
32
|
+
if args.command == "schema":
|
|
33
|
+
print("1.0")
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
if args.command == "snapshot":
|
|
37
|
+
snapshot = collect_snapshot(include_identity=args.include_identity)
|
|
38
|
+
if args.json:
|
|
39
|
+
print(to_json(snapshot))
|
|
40
|
+
elif args.markdown:
|
|
41
|
+
print(to_markdown(snapshot), end="")
|
|
42
|
+
else:
|
|
43
|
+
print(f"OS: {snapshot.os}")
|
|
44
|
+
print(f"Kernel: {snapshot.kernel}")
|
|
45
|
+
print(f"Architecture: {snapshot.architecture}")
|
|
46
|
+
print(
|
|
47
|
+
f"CPU: {snapshot.cpu.logical_cores} logical / "
|
|
48
|
+
f"{snapshot.cpu.physical_cores} physical"
|
|
49
|
+
)
|
|
50
|
+
print(f"CPU load: {snapshot.cpu.load_percent}%")
|
|
51
|
+
print(f"Memory: {snapshot.memory.used_percent}% used")
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
if args.command == "doctor":
|
|
55
|
+
checks = run_checks(collect_snapshot())
|
|
56
|
+
if args.json:
|
|
57
|
+
print(json.dumps([check.__dict__ for check in checks], indent=2))
|
|
58
|
+
else:
|
|
59
|
+
for check in checks:
|
|
60
|
+
print(f"[{check.status.upper()}] {check.message}")
|
|
61
|
+
return 0 if not any(check.status == "error" for check in checks) else 1
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
import getpass, platform, time
|
|
3
|
+
import psutil
|
|
4
|
+
from .models import CpuInfo, DiskInfo, MemoryInfo, Snapshot
|
|
5
|
+
SCHEMA_VERSION = "1.0"
|
|
6
|
+
def collect_snapshot(*, include_identity: bool = False) -> Snapshot:
|
|
7
|
+
vm = psutil.virtual_memory()
|
|
8
|
+
disks, seen = [], set()
|
|
9
|
+
for part in psutil.disk_partitions(all=False):
|
|
10
|
+
if part.mountpoint in seen: continue
|
|
11
|
+
seen.add(part.mountpoint)
|
|
12
|
+
try:
|
|
13
|
+
u = psutil.disk_usage(part.mountpoint)
|
|
14
|
+
disks.append(DiskInfo(part.mountpoint, u.total, u.free, u.percent))
|
|
15
|
+
except (OSError, PermissionError):
|
|
16
|
+
disks.append(DiskInfo(part.mountpoint, None, None, None))
|
|
17
|
+
return Snapshot(
|
|
18
|
+
collected_at=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
19
|
+
schema_version=SCHEMA_VERSION, os=platform.system(), kernel=platform.release(),
|
|
20
|
+
architecture=platform.machine(),
|
|
21
|
+
cpu=CpuInfo(psutil.cpu_count(), psutil.cpu_count(logical=False), psutil.cpu_percent(interval=0.05)),
|
|
22
|
+
memory=MemoryInfo(vm.total, vm.available, vm.used, vm.percent), disks=disks,
|
|
23
|
+
uptime_seconds=max(0.0, time.time() - psutil.boot_time()),
|
|
24
|
+
hostname=platform.node() if include_identity else None,
|
|
25
|
+
username=getpass.getuser() if include_identity else None)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .models import HealthCheck, Snapshot
|
|
2
|
+
def run_checks(snapshot: Snapshot) -> list[HealthCheck]:
|
|
3
|
+
checks = []
|
|
4
|
+
mem = snapshot.memory.used_percent
|
|
5
|
+
if mem is None: checks.append(HealthCheck("memory.available","unknown","Memory usage is unavailable."))
|
|
6
|
+
elif mem >= 95: checks.append(HealthCheck("memory.usage","error","Memory usage is critically high.",{"used_percent":mem}))
|
|
7
|
+
elif mem >= 85: checks.append(HealthCheck("memory.usage","warning","Memory usage is high.",{"used_percent":mem}))
|
|
8
|
+
else: checks.append(HealthCheck("memory.usage","ok","Memory usage is within the normal threshold.",{"used_percent":mem}))
|
|
9
|
+
for d in snapshot.disks:
|
|
10
|
+
v=d.used_percent
|
|
11
|
+
if v is None: status,msg="unknown",f"Disk usage unavailable for {d.path}."
|
|
12
|
+
elif v >= 95: status,msg="error",f"Disk usage is critically high on {d.path}."
|
|
13
|
+
elif v >= 85: status,msg="warning",f"Disk usage is high on {d.path}."
|
|
14
|
+
else: status,msg="ok",f"Disk usage is within the normal threshold on {d.path}."
|
|
15
|
+
checks.append(HealthCheck(f"disk.usage:{d.path}",status,msg,{"used_percent":v}))
|
|
16
|
+
return checks
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from dataclasses import asdict, dataclass, field
|
|
2
|
+
from typing import Any
|
|
3
|
+
@dataclass(frozen=True)
|
|
4
|
+
class CpuInfo:
|
|
5
|
+
logical_cores: int | None
|
|
6
|
+
physical_cores: int | None
|
|
7
|
+
load_percent: float | None
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class MemoryInfo:
|
|
10
|
+
total_bytes: int | None
|
|
11
|
+
available_bytes: int | None
|
|
12
|
+
used_bytes: int | None
|
|
13
|
+
used_percent: float | None
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class DiskInfo:
|
|
16
|
+
path: str
|
|
17
|
+
total_bytes: int | None
|
|
18
|
+
free_bytes: int | None
|
|
19
|
+
used_percent: float | None
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Snapshot:
|
|
22
|
+
collected_at: str
|
|
23
|
+
schema_version: str
|
|
24
|
+
os: str
|
|
25
|
+
kernel: str
|
|
26
|
+
architecture: str
|
|
27
|
+
cpu: CpuInfo
|
|
28
|
+
memory: MemoryInfo
|
|
29
|
+
disks: list[DiskInfo] = field(default_factory=list)
|
|
30
|
+
uptime_seconds: float | None = None
|
|
31
|
+
hostname: str | None = None
|
|
32
|
+
username: str | None = None
|
|
33
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
def to_dict(self) -> dict[str, Any]:
|
|
35
|
+
return asdict(self)
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class HealthCheck:
|
|
38
|
+
id: str
|
|
39
|
+
status: str
|
|
40
|
+
message: str
|
|
41
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
from .models import Snapshot
|
|
4
|
+
def to_dict(snapshot: Snapshot) -> dict[str, Any]: return snapshot.to_dict()
|
|
5
|
+
def to_json(snapshot: Snapshot) -> str: return json.dumps(to_dict(snapshot), indent=2)
|
|
6
|
+
def to_markdown(snapshot: Snapshot) -> str:
|
|
7
|
+
rows=[("OS",snapshot.os),("Kernel",snapshot.kernel),("Architecture",snapshot.architecture),("CPU logical",snapshot.cpu.logical_cores),("CPU physical",snapshot.cpu.physical_cores),("CPU load",snapshot.cpu.load_percent),("Memory used",snapshot.memory.used_percent),("Uptime seconds",snapshot.uptime_seconds)]
|
|
8
|
+
out=["# PCPulse System Snapshot","","| Metric | Value |","|---|---|"]
|
|
9
|
+
out += [f"| {k} | {v if v is not None else 'unavailable'} |" for k,v in rows]
|
|
10
|
+
out += ["","## Disks"]
|
|
11
|
+
out += [f"- {d.path}: {d.used_percent if d.used_percent is not None else 'unavailable'}% used" for d in snapshot.disks]
|
|
12
|
+
return "\n".join(out)+"\n"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pcpulse import collect_snapshot
|
|
3
|
+
from pcpulse.serialization import to_json, to_markdown
|
|
4
|
+
def test_json_is_valid():
|
|
5
|
+
assert json.loads(to_json(collect_snapshot()))["schema_version"]=="1.0"
|
|
6
|
+
def test_markdown_contains_title():
|
|
7
|
+
assert "PCPulse System Snapshot" in to_markdown(collect_snapshot())
|