sysgraph 0.0.16__tar.gz → 0.0.18__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.
- sysgraph-0.0.18/PKG-INFO +159 -0
- sysgraph-0.0.18/README.md +128 -0
- sysgraph-0.0.18/pyproject.toml +66 -0
- sysgraph-0.0.18/src/sysgraph/__init__.py +1 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/app.py +6 -1
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/constants.py +2 -2
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/discovery.py +41 -12
- sysgraph-0.0.18/src/sysgraph/dist/assets/index-BLbtXZbQ.css +1 -0
- sysgraph-0.0.18/src/sysgraph/dist/assets/index-Dq5MCDEE.js +737 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/dist/index.html +6 -4
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/main.py +4 -1
- sysgraph-0.0.18/src/sysgraph/temp/congress_network/compute_vc.py +31 -0
- sysgraph-0.0.18/src/sysgraph/temp/congress_network/convert_to_graph_format.py +85 -0
- sysgraph-0.0.18/src/sysgraph/temp/congress_network/histogram_weights.py +36 -0
- sysgraph-0.0.18/src/sysgraph/temp/congress_network/viral_centrality.py +120 -0
- sysgraph-0.0.18/src/sysgraph/temp/voles_network/convert_voles_to_graph_format.py +89 -0
- sysgraph-0.0.18/src/sysgraph/tests/test_discovery.py +19 -0
- sysgraph-0.0.18/src/sysgraph/tests/test_graph.py +132 -0
- sysgraph-0.0.18/src/sysgraph.egg-info/PKG-INFO +159 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph.egg-info/SOURCES.txt +9 -4
- sysgraph-0.0.18/src/sysgraph.egg-info/top_level.txt +2 -0
- sysgraph-0.0.16/PKG-INFO +0 -142
- sysgraph-0.0.16/README.md +0 -99
- sysgraph-0.0.16/pyproject.toml +0 -19
- sysgraph-0.0.16/setup.py +0 -91
- sysgraph-0.0.16/src/sysgraph/__init__.py +0 -1
- sysgraph-0.0.16/src/sysgraph/dist/assets/index-D4kwmDWy.css +0 -1
- sysgraph-0.0.16/src/sysgraph/dist/assets/index-D5t1fx8g.js +0 -735
- sysgraph-0.0.16/src/sysgraph.egg-info/PKG-INFO +0 -142
- sysgraph-0.0.16/src/sysgraph.egg-info/not-zip-safe +0 -1
- sysgraph-0.0.16/src/sysgraph.egg-info/top_level.txt +0 -1
- {sysgraph-0.0.16 → sysgraph-0.0.18}/LICENSE +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/MANIFEST.in +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/setup.cfg +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/__main__.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/dist/assets/icon-TKtfQOgj.png +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/graph.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph/model.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph.egg-info/dependency_links.txt +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph.egg-info/entry_points.txt +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.18}/src/sysgraph.egg-info/requires.txt +0 -0
sysgraph-0.0.18/PKG-INFO
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sysgraph
|
|
3
|
+
Version: 0.0.18
|
|
4
|
+
Summary: Visualizer for processes and their interconnections
|
|
5
|
+
Author-email: Eugene Gubenkov <gubenkoved@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/gubenkoved/sysgraph
|
|
8
|
+
Project-URL: Issues, https://github.com/gubenkoved/sysgraph/issues
|
|
9
|
+
Keywords: process,visualization,graph,ipc,system,monitoring,cross-platform,macos,linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Classifier: Topic :: System :: Systems Administration
|
|
19
|
+
Classifier: Environment :: Web Environment
|
|
20
|
+
Classifier: Framework :: FastAPI
|
|
21
|
+
Classifier: Intended Audience :: Developers
|
|
22
|
+
Classifier: Intended Audience :: System Administrators
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: psutil
|
|
27
|
+
Requires-Dist: coloredlogs
|
|
28
|
+
Requires-Dist: fastapi>=0.95
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.20
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# sysgraph
|
|
33
|
+
|
|
34
|
+
An interactive force-directed **network graph visualizer** for the browser — with two modes:
|
|
35
|
+
|
|
36
|
+
- **Import any graph** — load any JSON graph (nodes + edges) to explore and visualize it interactively
|
|
37
|
+
- **Live process graph** — discover running OS processes and their inter-process communication channels in real time (cross-platform; richest on Linux)
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/sysgraph/)
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- **Import any graph** — load a JSON file with nodes and edges to visualize any network, social graph, dependency tree, or dataset
|
|
46
|
+
- **Export/Import** — save and reload graph snapshots as JSON; use the sample at [`data/simplest-graph.json`](data/simplest-graph.json) as a format reference
|
|
47
|
+
- **Interactive graph** — force-directed graph rendered in the browser with zoom, pan, drag, and search
|
|
48
|
+
- **Fuzzy search** — find nodes by any property
|
|
49
|
+
- **Adjacency filtering** — right-click a node to show only its neighbors
|
|
50
|
+
- **Configurable** — tune d3 force parameters, colors, and type filters via the settings panel
|
|
51
|
+
- **Process discovery** — enumerates running OS processes and their parent-child relationships (cross-platform via psutil)
|
|
52
|
+
- **IPC visualization** — discovers TCP/UDP connections (all platforms), Unix domain sockets and pipes (Linux only)
|
|
53
|
+
- **Real-time** — fetch the latest process graph on demand via the web UI
|
|
54
|
+
|
|
55
|
+
## Demo
|
|
56
|
+
|
|
57
|
+
[Demo](https://github.com/user-attachments/assets/7d19daca-042c-43f1-bedd-4d74344e1e89)
|
|
58
|
+
|
|
59
|
+
## Requirements
|
|
60
|
+
|
|
61
|
+
- **Python ≥ 3.12**
|
|
62
|
+
- **Linux, macOS, or Windows** — process and network discovery works on all platforms via psutil; Unix domain sockets and pipe discovery require Linux
|
|
63
|
+
- Root/sudo recommended for full process visibility on Linux/macOS
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install sysgraph
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Start the web server (default: http://localhost:8000)
|
|
75
|
+
sysgraph
|
|
76
|
+
|
|
77
|
+
# Specify a custom port
|
|
78
|
+
sysgraph --port 9000
|
|
79
|
+
|
|
80
|
+
# Or run as a module
|
|
81
|
+
python -m sysgraph
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Open your browser to the displayed URL.
|
|
85
|
+
|
|
86
|
+
### Visualize your own graph
|
|
87
|
+
|
|
88
|
+
Use the **Import** button in the UI to load any JSON file in the following format:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"nodes": [
|
|
93
|
+
{"id": "1", "type": "person", "properties": {"name": "Alice"}},
|
|
94
|
+
{"id": "2", "type": "person", "properties": {"name": "Bob"}}
|
|
95
|
+
],
|
|
96
|
+
"edges": [
|
|
97
|
+
{"source_id": "1", "target_id": "2", "type": "knows", "properties": {}}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See [`data/simplest-graph.json`](data/simplest-graph.json) for a minimal example.
|
|
103
|
+
|
|
104
|
+
### Live process graph
|
|
105
|
+
|
|
106
|
+
For full visibility into all processes and their connections, run with elevated privileges (Linux/macOS):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
sudo sysgraph
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Docker
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
docker run --rm -it --pid=host --net=host gubenkoved/sysgraph
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The `--pid=host` and `--net=host` flags allow the container to see host processes and network connections.
|
|
119
|
+
|
|
120
|
+
## How It Works
|
|
121
|
+
|
|
122
|
+
1. The **browser frontend** renders interactive force-directed graphs using [force-graph](https://github.com/vasturiano/force-graph) with d3 physics simulation.
|
|
123
|
+
2. Graphs can be **imported from JSON** directly in the browser, or fetched live from the backend.
|
|
124
|
+
3. The **FastAPI backend** uses `psutil` to discover processes and network connections (cross-platform), plus Linux-specific APIs (`/proc`, `ss`) for pipe and Unix domain socket discovery, building a graph served via `GET /api/graph`.
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
### Prerequisites
|
|
129
|
+
- Python ≥ 3.12, Docker (for frontend builds)
|
|
130
|
+
- Node.js 22 runs inside Docker; no host installation required
|
|
131
|
+
|
|
132
|
+
### Backend
|
|
133
|
+
```bash
|
|
134
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
135
|
+
pip install -e . && pip install -r requirements-dev.in
|
|
136
|
+
python src/sysgraph/app.py # → http://localhost:8000
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Frontend (TypeScript + Vite)
|
|
140
|
+
```bash
|
|
141
|
+
./scripts/build-ui.sh # production build → src/sysgraph/dist/
|
|
142
|
+
./scripts/dev-ui.sh # Vite dev server with HMR on :5173
|
|
143
|
+
./scripts/typecheck-ui.sh # TypeScript type checking
|
|
144
|
+
./scripts/lint-ui.sh # Biome linter (pass --fix to auto-fix)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Tests
|
|
148
|
+
```bash
|
|
149
|
+
pytest src/sysgraph/tests/
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Python linting
|
|
153
|
+
```bash
|
|
154
|
+
./scripts/lint.sh # ruff check + ruff format + isort
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# sysgraph
|
|
2
|
+
|
|
3
|
+
An interactive force-directed **network graph visualizer** for the browser — with two modes:
|
|
4
|
+
|
|
5
|
+
- **Import any graph** — load any JSON graph (nodes + edges) to explore and visualize it interactively
|
|
6
|
+
- **Live process graph** — discover running OS processes and their inter-process communication channels in real time (cross-platform; richest on Linux)
|
|
7
|
+
|
|
8
|
+
[](https://pypi.org/project/sysgraph/)
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Import any graph** — load a JSON file with nodes and edges to visualize any network, social graph, dependency tree, or dataset
|
|
15
|
+
- **Export/Import** — save and reload graph snapshots as JSON; use the sample at [`data/simplest-graph.json`](data/simplest-graph.json) as a format reference
|
|
16
|
+
- **Interactive graph** — force-directed graph rendered in the browser with zoom, pan, drag, and search
|
|
17
|
+
- **Fuzzy search** — find nodes by any property
|
|
18
|
+
- **Adjacency filtering** — right-click a node to show only its neighbors
|
|
19
|
+
- **Configurable** — tune d3 force parameters, colors, and type filters via the settings panel
|
|
20
|
+
- **Process discovery** — enumerates running OS processes and their parent-child relationships (cross-platform via psutil)
|
|
21
|
+
- **IPC visualization** — discovers TCP/UDP connections (all platforms), Unix domain sockets and pipes (Linux only)
|
|
22
|
+
- **Real-time** — fetch the latest process graph on demand via the web UI
|
|
23
|
+
|
|
24
|
+
## Demo
|
|
25
|
+
|
|
26
|
+
[Demo](https://github.com/user-attachments/assets/7d19daca-042c-43f1-bedd-4d74344e1e89)
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- **Python ≥ 3.12**
|
|
31
|
+
- **Linux, macOS, or Windows** — process and network discovery works on all platforms via psutil; Unix domain sockets and pipe discovery require Linux
|
|
32
|
+
- Root/sudo recommended for full process visibility on Linux/macOS
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install sysgraph
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Start the web server (default: http://localhost:8000)
|
|
44
|
+
sysgraph
|
|
45
|
+
|
|
46
|
+
# Specify a custom port
|
|
47
|
+
sysgraph --port 9000
|
|
48
|
+
|
|
49
|
+
# Or run as a module
|
|
50
|
+
python -m sysgraph
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Open your browser to the displayed URL.
|
|
54
|
+
|
|
55
|
+
### Visualize your own graph
|
|
56
|
+
|
|
57
|
+
Use the **Import** button in the UI to load any JSON file in the following format:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"nodes": [
|
|
62
|
+
{"id": "1", "type": "person", "properties": {"name": "Alice"}},
|
|
63
|
+
{"id": "2", "type": "person", "properties": {"name": "Bob"}}
|
|
64
|
+
],
|
|
65
|
+
"edges": [
|
|
66
|
+
{"source_id": "1", "target_id": "2", "type": "knows", "properties": {}}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See [`data/simplest-graph.json`](data/simplest-graph.json) for a minimal example.
|
|
72
|
+
|
|
73
|
+
### Live process graph
|
|
74
|
+
|
|
75
|
+
For full visibility into all processes and their connections, run with elevated privileges (Linux/macOS):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
sudo sysgraph
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Docker
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
docker run --rm -it --pid=host --net=host gubenkoved/sysgraph
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The `--pid=host` and `--net=host` flags allow the container to see host processes and network connections.
|
|
88
|
+
|
|
89
|
+
## How It Works
|
|
90
|
+
|
|
91
|
+
1. The **browser frontend** renders interactive force-directed graphs using [force-graph](https://github.com/vasturiano/force-graph) with d3 physics simulation.
|
|
92
|
+
2. Graphs can be **imported from JSON** directly in the browser, or fetched live from the backend.
|
|
93
|
+
3. The **FastAPI backend** uses `psutil` to discover processes and network connections (cross-platform), plus Linux-specific APIs (`/proc`, `ss`) for pipe and Unix domain socket discovery, building a graph served via `GET /api/graph`.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
### Prerequisites
|
|
98
|
+
- Python ≥ 3.12, Docker (for frontend builds)
|
|
99
|
+
- Node.js 22 runs inside Docker; no host installation required
|
|
100
|
+
|
|
101
|
+
### Backend
|
|
102
|
+
```bash
|
|
103
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
104
|
+
pip install -e . && pip install -r requirements-dev.in
|
|
105
|
+
python src/sysgraph/app.py # → http://localhost:8000
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Frontend (TypeScript + Vite)
|
|
109
|
+
```bash
|
|
110
|
+
./scripts/build-ui.sh # production build → src/sysgraph/dist/
|
|
111
|
+
./scripts/dev-ui.sh # Vite dev server with HMR on :5173
|
|
112
|
+
./scripts/typecheck-ui.sh # TypeScript type checking
|
|
113
|
+
./scripts/lint-ui.sh # Biome linter (pass --fix to auto-fix)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Tests
|
|
117
|
+
```bash
|
|
118
|
+
pytest src/sysgraph/tests/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Python linting
|
|
122
|
+
```bash
|
|
123
|
+
./scripts/lint.sh # ruff check + ruff format + isort
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sysgraph"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Visualizer for processes and their interconnections"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Eugene Gubenkov", email = "gubenkoved@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
keywords = ["process", "visualization", "graph", "ipc", "system", "monitoring", "cross-platform", "macos", "linux"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Operating System :: Microsoft :: Windows",
|
|
22
|
+
"Topic :: System :: Monitoring",
|
|
23
|
+
"Topic :: System :: Systems Administration",
|
|
24
|
+
"Environment :: Web Environment",
|
|
25
|
+
"Framework :: FastAPI",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"Intended Audience :: System Administrators",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"psutil",
|
|
31
|
+
"coloredlogs",
|
|
32
|
+
"fastapi>=0.95",
|
|
33
|
+
"uvicorn[standard]>=0.20",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Source = "https://github.com/gubenkoved/sysgraph"
|
|
38
|
+
Issues = "https://github.com/gubenkoved/sysgraph/issues"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
sysgraph = "sysgraph.app:main"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.dynamic]
|
|
44
|
+
version = { attr = "sysgraph.__version__" }
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.package-data]
|
|
50
|
+
sysgraph = ["dist/*", "dist/**/*"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 79
|
|
54
|
+
target-version = "py312"
|
|
55
|
+
# Tune ruff rules as needed. Ruff supports many checks and can also format (`ruff format`).
|
|
56
|
+
# See https://beta.ruff.rs/docs/configuration/ for config options.
|
|
57
|
+
exclude = [".venv", "build", "dist", "__pycache__"]
|
|
58
|
+
|
|
59
|
+
[tool.isort]
|
|
60
|
+
profile = "black"
|
|
61
|
+
multi_line_output = 3
|
|
62
|
+
include_trailing_comma = true
|
|
63
|
+
force_grid_wrap = 0
|
|
64
|
+
use_parentheses = true
|
|
65
|
+
ensure_newline_before_comments = true
|
|
66
|
+
line_length = 88
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.18"
|
|
@@ -18,9 +18,14 @@ from sysgraph.discovery import build_graph
|
|
|
18
18
|
LOGGER = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
LOG_FORMAT = (
|
|
22
|
+
"%(asctime)s.%(msecs)03d %(name)s[%(process)d] %(levelname)s %(message)s"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
21
26
|
@asynccontextmanager
|
|
22
27
|
async def lifespan(app):
|
|
23
|
-
coloredlogs.install()
|
|
28
|
+
coloredlogs.install(fmt=LOG_FORMAT)
|
|
24
29
|
logging.info(f"init for process with PID {os.getpid()}")
|
|
25
30
|
yield
|
|
26
31
|
logging.info(f"shutdown for process with PID {os.getpid()}")
|
|
@@ -20,8 +20,8 @@ def process_node_id(pid: int) -> str:
|
|
|
20
20
|
return f"{NODE_PROCESS}::{pid}"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def uds_node_id(
|
|
24
|
-
return f"{NODE_UDS}::{
|
|
23
|
+
def uds_node_id(key: str) -> str:
|
|
24
|
+
return f"{NODE_UDS}::{key}"
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def pipe_node_id(inode: str) -> str:
|
|
@@ -2,6 +2,8 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
4
|
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
5
7
|
from collections import defaultdict
|
|
6
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
9
|
from pathlib import Path
|
|
@@ -71,7 +73,7 @@ def discover_processes() -> list[Process]:
|
|
|
71
73
|
if mem_info:
|
|
72
74
|
p.memory_rss = mem_info.rss
|
|
73
75
|
p.memory_vms = mem_info.vms
|
|
74
|
-
p.memory_shared = mem_info
|
|
76
|
+
p.memory_shared = getattr(mem_info, "shared", None)
|
|
75
77
|
|
|
76
78
|
p.environment = info.get("environ")
|
|
77
79
|
|
|
@@ -81,15 +83,18 @@ def discover_processes() -> list[Process]:
|
|
|
81
83
|
|
|
82
84
|
|
|
83
85
|
def discover_unix_sockets() -> list[UnixDomainSocket]:
|
|
84
|
-
"""Discover Unix Domain Sockets
|
|
86
|
+
"""Discover Unix Domain Sockets by parsing ``ss -xp`` output.
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
find connected pairs of processes.
|
|
88
|
+
This is a Linux-only feature. On other platforms an empty list is
|
|
89
|
+
returned without raising an error.
|
|
89
90
|
|
|
90
91
|
Returns:
|
|
91
|
-
list[UnixDomainSocket]:
|
|
92
|
+
list[UnixDomainSocket]: Discovered UDS sockets.
|
|
92
93
|
"""
|
|
94
|
+
if sys.platform != "linux":
|
|
95
|
+
LOGGER.debug("UDS discovery skipped (only supported on Linux)")
|
|
96
|
+
return []
|
|
97
|
+
|
|
93
98
|
result = subprocess.run(
|
|
94
99
|
["ss", "-xp"], capture_output=True, text=True, check=False
|
|
95
100
|
)
|
|
@@ -98,11 +103,15 @@ def discover_unix_sockets() -> list[UnixDomainSocket]:
|
|
|
98
103
|
|
|
99
104
|
# example line:
|
|
100
105
|
# users:(("dbus-daemon",pid=1950,fd=12))
|
|
101
|
-
def parse_proccess(
|
|
106
|
+
def parse_proccess(
|
|
107
|
+
s: str,
|
|
108
|
+
) -> list[UnixDomainSocketProcRef]:
|
|
102
109
|
processes = []
|
|
103
110
|
|
|
104
111
|
for match in re.finditer(
|
|
105
|
-
r'\("(?P<name>[^"]+)",pid=(?P<pid>[0-9]+),
|
|
112
|
+
r'\("(?P<name>[^"]+)",pid=(?P<pid>[0-9]+),'
|
|
113
|
+
r"fd=(?P<fd>[0-9]+)\)",
|
|
114
|
+
s,
|
|
106
115
|
):
|
|
107
116
|
ref = UnixDomainSocketProcRef(pid=int(match.group("pid")))
|
|
108
117
|
ref.name = match.group("name")
|
|
@@ -140,7 +149,8 @@ def discover_connected_uds(
|
|
|
140
149
|
) -> list[UnixDomainSocketConnection]:
|
|
141
150
|
"""Discover connected UDS pairs from the list of discovered UDS sockets.
|
|
142
151
|
|
|
143
|
-
Each connection will have two UDS sockets with opposite local/peer
|
|
152
|
+
Each connection will have two UDS sockets with opposite local/peer
|
|
153
|
+
inodes.
|
|
144
154
|
|
|
145
155
|
Args:
|
|
146
156
|
sockets (list[UnixDomainSocket]): List of discovered UDS sockets.
|
|
@@ -158,7 +168,10 @@ def discover_connected_uds(
|
|
|
158
168
|
for uds in sockets:
|
|
159
169
|
if (uds.peer_inode, uds.local_inode) in inode_map:
|
|
160
170
|
peer_uds = inode_map[(uds.peer_inode, uds.local_inode)]
|
|
161
|
-
if (
|
|
171
|
+
if (
|
|
172
|
+
uds.local_inode,
|
|
173
|
+
uds.peer_inode,
|
|
174
|
+
) not in visited and (
|
|
162
175
|
uds.peer_inode,
|
|
163
176
|
uds.local_inode,
|
|
164
177
|
) not in visited:
|
|
@@ -174,8 +187,18 @@ def discover_connected_uds(
|
|
|
174
187
|
|
|
175
188
|
|
|
176
189
|
def get_processes_open_files() -> dict[int, list[ProcessOpenFile]]:
|
|
177
|
-
"""Read open file descriptors
|
|
190
|
+
"""Read open file descriptors from /proc to discover pipes.
|
|
191
|
+
|
|
192
|
+
This is a Linux-only feature that reads ``/proc/[pid]/fd`` and
|
|
193
|
+
``/proc/[pid]/fdinfo``. On other platforms an empty mapping is
|
|
194
|
+
returned without raising an error.
|
|
195
|
+
"""
|
|
178
196
|
result_map: dict[int, list[ProcessOpenFile]] = defaultdict(list)
|
|
197
|
+
|
|
198
|
+
if sys.platform != "linux":
|
|
199
|
+
LOGGER.debug("pipe discovery skipped (only supported on Linux)")
|
|
200
|
+
return result_map
|
|
201
|
+
|
|
179
202
|
proc_path = Path("/proc")
|
|
180
203
|
|
|
181
204
|
for pid_dir in proc_path.iterdir():
|
|
@@ -305,7 +328,7 @@ def _add_uds_nodes(
|
|
|
305
328
|
label = f"uds:[{inode}]"
|
|
306
329
|
|
|
307
330
|
node = graph.add_node(
|
|
308
|
-
uds_node_id(inode),
|
|
331
|
+
uds_node_id(str(inode)),
|
|
309
332
|
NODE_UDS,
|
|
310
333
|
properties={
|
|
311
334
|
"label": label,
|
|
@@ -513,6 +536,8 @@ def _add_network_nodes(
|
|
|
513
536
|
|
|
514
537
|
|
|
515
538
|
def build_graph(discover_uds_connectivity: bool = True) -> Graph:
|
|
539
|
+
started_at = time.monotonic()
|
|
540
|
+
|
|
516
541
|
graph = Graph()
|
|
517
542
|
|
|
518
543
|
# run independent discovery steps in parallel (all I/O-bound)
|
|
@@ -532,4 +557,8 @@ def build_graph(discover_uds_connectivity: bool = True) -> Graph:
|
|
|
532
557
|
_add_pipe_nodes(graph, open_files_map, pid_to_node)
|
|
533
558
|
_add_network_nodes(graph, processes, all_net_connections, pid_to_node)
|
|
534
559
|
|
|
560
|
+
LOGGER.info(
|
|
561
|
+
f"discovery completed in {time.monotonic() - started_at:.2f} seconds"
|
|
562
|
+
)
|
|
563
|
+
|
|
535
564
|
return graph
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--z-toolbar: 100;--z-search-help: 150;--z-context-menu: 200;--z-loading-overlay: 300;--z-details-panel: 50;--z-selection-overlay: 10;--z-version-badge: 10;--z-zoom-indicator: 10;--bg-toolbar: rgba(255, 255, 255, .95);--bg-panel: rgba(255, 255, 255, .97);--border-subtle: rgba(0, 0, 0, .08);--border-light: rgba(0, 0, 0, .1);--border-medium: rgba(0, 0, 0, .12);--shadow-xs: rgba(0, 0, 0, .08);--shadow-sm: rgba(0, 0, 0, .14);--shadow-md: rgba(0, 0, 0, .15);--accent-primary: #1a56db;--accent-info: #1a73e8;--accent-danger: #dc2626;--toolbar-height: 48px;--spacing-xs: 4px;--spacing-sm: 6px;--spacing-md: 8px;--spacing-lg: 16px;--font-family: "Ubuntu", "Segoe UI", "Arial", sans-serif;--font-size-xs: 11px;--font-size-sm: 12px;--font-size-md: 13px;--radius-sm: 4px;--radius-md: 8px;--radius-lg: 12px;--button-height: 28px;--icon-button-size: 24px;--tp-base-background-color: hsla(230, 5%, 90%, 1);--tp-base-shadow-color: hsla(0, 0%, 0%, .1);--tp-button-background-color: hsla(230, 7%, 75%, 1);--tp-button-background-color-active: hsla(230, 7%, 60%, 1);--tp-button-background-color-focus: hsla(230, 7%, 65%, 1);--tp-button-background-color-hover: hsla(230, 7%, 70%, 1);--tp-button-foreground-color: hsla(230, 10%, 30%, 1);--tp-container-background-color: hsla(230, 15%, 30%, .2);--tp-container-background-color-active: hsla(230, 15%, 30%, .32);--tp-container-background-color-focus: hsla(230, 15%, 30%, .28);--tp-container-background-color-hover: hsla(230, 15%, 30%, .24);--tp-container-foreground-color: hsla(230, 10%, 30%, 1);--tp-groove-foreground-color: hsla(230, 15%, 30%, .1);--tp-input-background-color: hsla(230, 15%, 30%, .1);--tp-input-background-color-active: hsla(230, 15%, 30%, .22);--tp-input-background-color-focus: hsla(230, 15%, 30%, .18);--tp-input-background-color-hover: hsla(230, 15%, 30%, .14);--tp-input-foreground-color: hsla(230, 10%, 30%, 1);--tp-label-foreground-color: hsla(230, 10%, 30%, .7);--tp-monitor-background-color: hsla(230, 15%, 30%, .1);--tp-monitor-foreground-color: hsla(230, 10%, 30%, .5)}html,body{height:100%;margin:0;overflow:hidden;font-family:var(--font-family)}#toolbar{position:fixed;top:0;left:0;right:0;height:var(--toolbar-height);z-index:var(--z-toolbar);background:var(--bg-toolbar);border-bottom:1px solid var(--border-light);display:flex;align-items:center;padding:0 var(--spacing-lg);gap:var(--spacing-sm);box-shadow:0 1px 3px var(--shadow-xs)}#toolbar md-outlined-button{--md-outlined-button-container-height: var(--button-height);--md-outlined-button-label-text-size: var(--font-size-sm);--md-outlined-button-label-text-weight: 500;--md-outlined-button-icon-size: 16px;--md-outlined-button-leading-space: 10px;--md-outlined-button-trailing-space: 12px;--md-outlined-button-icon-offset: -2px;--md-outlined-button-container-shape: var(--radius-sm)}md-outlined-button.active{--md-outlined-button-container-color: #d0e1ff;--md-outlined-button-label-text-color: var(--accent-primary);--md-outlined-button-outline-color: var(--accent-primary);--md-outlined-button-icon-color: var(--accent-primary)}md-outlined-button.danger{--md-outlined-button-label-text-color: var(--accent-danger);--md-outlined-button-outline-color: var(--accent-danger);--md-outlined-button-icon-color: var(--accent-danger)}.toolbar-separator{width:1px;height:var(--icon-button-size);background:var(--border-light)}#searchInput{width:240px;height:var(--button-height);--md-outlined-text-field-container-shape: var(--radius-sm);--md-outlined-text-field-top-space: var(--spacing-xs);--md-outlined-text-field-bottom-space: var(--spacing-xs);--md-outlined-text-field-input-text-size: var(--font-size-sm);--md-outlined-text-field-label-text-size: var(--font-size-sm)}#searchHelpTrigger{--md-icon-button-icon-size: 18px;--md-icon-button-state-layer-height: var(--button-height);--md-icon-button-state-layer-width: var(--button-height);height:var(--button-height);width:var(--button-height)}.search-help-anchor{position:relative;display:inline-flex}#searchHelp{display:none;position:absolute;top:100%;left:0;margin-top:var(--spacing-xs);z-index:var(--z-search-help);border:1px solid var(--border-medium);border-radius:var(--radius-md);padding:10px 14px;box-shadow:0 4px 12px var(--shadow-md);font-size:var(--font-size-sm);line-height:1.5;background:#1e293b;color:#fff}#searchHelp.open{display:block}#searchHelp code{background:#ffffff1f;padding:1px var(--spacing-xs);border-radius:3px}#content{position:fixed;top:var(--toolbar-height);left:0;right:0;bottom:0}#graph{width:100%;height:100%;position:relative}.details-panel{position:absolute;top:var(--spacing-md);left:var(--spacing-md);width:fit-content;max-width:420px;height:auto;min-width:200px;min-height:120px;max-height:min(350px,calc(100% - var(--spacing-lg)));z-index:var(--z-details-panel);background:var(--bg-panel);box-shadow:0 2px 12px var(--shadow-sm);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);display:none;flex-direction:column;overflow:hidden;resize:both}.details-panel.open{display:flex}.details-panel .panel-header{display:flex;align-items:center;height:var(--button-height);padding:0 2px 0 10px;border-bottom:1px solid var(--border-subtle);flex-shrink:0;cursor:grab;-webkit-user-select:none;user-select:none}.details-panel .panel-header:active{cursor:grabbing}.details-panel .panel-title{font-size:var(--font-size-sm);font-weight:500;color:#333;flex:1}.details-panel md-icon-button{--md-icon-button-icon-size: 16px;--md-icon-button-state-layer-height: var(--icon-button-size);--md-icon-button-state-layer-width: var(--icon-button-size);height:var(--icon-button-size);width:var(--icon-button-size)}.details-panel .panel-body{flex:1;overflow-y:auto;min-height:0;padding:var(--spacing-md);font-family:var(--font-family)}#detailsPanel{border-left:3px solid var(--accent-info)}#detailsPanel .panel-header{background:#1a73e80f}#contextMenu{display:none;position:fixed;z-index:var(--z-context-menu);background:var(--bg-panel);border:1px solid var(--border-medium);border-radius:var(--spacing-sm);padding:var(--spacing-xs) 0;box-shadow:0 4px 12px var(--shadow-md);min-width:160px;font-size:var(--font-size-md)}.context-menu-item{padding:var(--spacing-sm) 14px;cursor:pointer;-webkit-user-select:none;user-select:none}.context-menu-item:hover{background:#0000000f}.context-menu-divider{height:1px;margin:var(--spacing-xs) 0;background:var(--border-medium)}#loading-overlay{display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:var(--z-loading-overlay);background:#0000008c;color:#fff;font-family:var(--font-family);font-size:14px;padding:8px 18px;border-radius:var(--radius-md);pointer-events:none}#loading-overlay.visible{display:block}.tp-rotv{font-family:var(--font-family)!important;opacity:.9}#settingsPane{max-height:calc(100% - 30px);position:absolute;overflow-y:auto;right:10px;top:10px}#version-badge{position:fixed;bottom:var(--spacing-sm);right:var(--spacing-md);font-size:var(--font-size-xs);color:#0000004d;pointer-events:none;z-index:var(--z-version-badge);font-family:var(--font-family)}#zoom-indicator{position:fixed;bottom:var(--spacing-md);left:var(--spacing-md);z-index:var(--z-zoom-indicator);display:flex;align-items:center;gap:0;background:var(--bg-toolbar);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:0 1px 4px var(--shadow-xs);padding:0 var(--spacing-xs)}#zoom-indicator md-icon-button{--md-icon-button-icon-size: 16px;--md-icon-button-state-layer-height: var(--button-height);--md-icon-button-state-layer-width: var(--button-height);height:var(--button-height);width:var(--button-height)}#zoomLevel{font-size:var(--font-size-xs);font-family:var(--font-family);color:#555;min-width:34px;text-align:center;-webkit-user-select:none;user-select:none;cursor:pointer;border-radius:var(--radius-sm);padding:2px var(--spacing-xs);transition:background .1s}#zoomLevel:hover{background:#0000000f}.muted{color:#666}hr{border:none;border-top:1px solid var(--border-subtle);margin:var(--spacing-sm) 0}
|