ppop 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.
ppop-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zhongwang Lun
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.
ppop-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: ppop
3
+ Version: 0.1.0
4
+ Summary: An interactive PPU process viewer and resource monitor, inspired by nvitop.
5
+ Author-email: Zhongwang Lun <lunandcnn@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: ppu,monitor,process-viewer,tui
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console :: Curses
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: System :: Monitoring
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: psutil>=5.6.6
18
+ Dynamic: license-file
19
+
20
+ # ppop
21
+
22
+ An interactive PPU process viewer and resource monitor for the terminal, inspired by [nvitop](https://github.com/XuehaiPan/nvitop).
23
+
24
+ ## Features
25
+
26
+ - Real-time PPU device monitoring (utilization, memory, temperature, power)
27
+ - Host CPU and memory usage display
28
+ - Per-process PPU memory tracking
29
+ - Interactive curses-based TUI with color-coded utilization bars
30
+ - Non-interactive one-shot mode for scripts and logging
31
+
32
+ ## Demo
33
+
34
+ ```
35
+ +-------------------------------------------------------------------------------+
36
+ | ppop - PPU Monitor Driver Version: 1.5.5-7a3ae6 |
37
+ +-----------------------------------+----------------------+-------------------+
38
+ | PPU Name | Memory-Usage | PPU-Util |
39
+ +===================================+======================+===================+
40
+ | 0 PPU-ZW810E 32C 71W / 400W | 2MiB / 98304MiB | 0% Default |
41
+ | 1 PPU-ZW810E 32C 75W / 400W | 2MiB / 98304MiB | 0% Default |
42
+ +-----------------------------------+----------------------+-------------------+
43
+
44
+ +-------------------------------------------------------------------------------+
45
+ | Host: |
46
+ | CPU [|||| ] 21.3% (32 cores) MEM [|||||| ] 32.1GiB/64.0GiB (50.2%)
47
+ +-------------------------------------------------------------------------------+
48
+
49
+ +-------------------------------------------------------------------------------+
50
+ | Processes: |
51
+ | PPU PID USER PPU-MEM CPU% MEM% TIME Command |
52
+ +===============================================================================+
53
+ | 0 12345 user 8192MiB 3.2% 12.6% 1:23 python train.py |
54
+ +-------------------------------------------------------------------------------+
55
+ ```
56
+
57
+ ## Requirements
58
+
59
+ - Python 3.8+
60
+ - PPU device with `ppu-smi` installed (default path: `/usr/local/PPU_SDK/ppu-smi/bin/ppu-smi`)
61
+
62
+ ## Installation
63
+
64
+ ### From source
65
+
66
+ ```bash
67
+ git clone https://github.com/your-username/ppop.git
68
+ cd ppop
69
+ pip install .
70
+ ```
71
+
72
+ ### From PyPI (coming soon)
73
+
74
+ ```bash
75
+ pip install ppop
76
+ ```
77
+
78
+ ## Usage
79
+
80
+ ```bash
81
+ # Interactive mode (real-time monitoring)
82
+ ppop
83
+
84
+ # One-shot mode (print once and exit)
85
+ ppop --once
86
+
87
+ # Custom refresh interval (default: 2s)
88
+ ppop -i 5
89
+ ```
90
+
91
+ ### Keyboard Shortcuts (interactive mode)
92
+
93
+ | Key | Action |
94
+ |-----|--------|
95
+ | `q` | Quit |
96
+ | `↑` / `k` | Scroll up |
97
+ | `↓` / `j` | Scroll down |
98
+ | `PgUp` / `PgDn` | Scroll by page |
99
+ | `r` | Force refresh |
100
+
101
+ ## Configuration
102
+
103
+ If `ppu-smi` is not at the default path, set the environment variable:
104
+
105
+ ```bash
106
+ export PPU_SMI_PATH=/path/to/ppu-smi
107
+ ```
108
+
109
+ ## License
110
+
111
+ [MIT](LICENSE)
ppop-0.1.0/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # ppop
2
+
3
+ An interactive PPU process viewer and resource monitor for the terminal, inspired by [nvitop](https://github.com/XuehaiPan/nvitop).
4
+
5
+ ## Features
6
+
7
+ - Real-time PPU device monitoring (utilization, memory, temperature, power)
8
+ - Host CPU and memory usage display
9
+ - Per-process PPU memory tracking
10
+ - Interactive curses-based TUI with color-coded utilization bars
11
+ - Non-interactive one-shot mode for scripts and logging
12
+
13
+ ## Demo
14
+
15
+ ```
16
+ +-------------------------------------------------------------------------------+
17
+ | ppop - PPU Monitor Driver Version: 1.5.5-7a3ae6 |
18
+ +-----------------------------------+----------------------+-------------------+
19
+ | PPU Name | Memory-Usage | PPU-Util |
20
+ +===================================+======================+===================+
21
+ | 0 PPU-ZW810E 32C 71W / 400W | 2MiB / 98304MiB | 0% Default |
22
+ | 1 PPU-ZW810E 32C 75W / 400W | 2MiB / 98304MiB | 0% Default |
23
+ +-----------------------------------+----------------------+-------------------+
24
+
25
+ +-------------------------------------------------------------------------------+
26
+ | Host: |
27
+ | CPU [|||| ] 21.3% (32 cores) MEM [|||||| ] 32.1GiB/64.0GiB (50.2%)
28
+ +-------------------------------------------------------------------------------+
29
+
30
+ +-------------------------------------------------------------------------------+
31
+ | Processes: |
32
+ | PPU PID USER PPU-MEM CPU% MEM% TIME Command |
33
+ +===============================================================================+
34
+ | 0 12345 user 8192MiB 3.2% 12.6% 1:23 python train.py |
35
+ +-------------------------------------------------------------------------------+
36
+ ```
37
+
38
+ ## Requirements
39
+
40
+ - Python 3.8+
41
+ - PPU device with `ppu-smi` installed (default path: `/usr/local/PPU_SDK/ppu-smi/bin/ppu-smi`)
42
+
43
+ ## Installation
44
+
45
+ ### From source
46
+
47
+ ```bash
48
+ git clone https://github.com/your-username/ppop.git
49
+ cd ppop
50
+ pip install .
51
+ ```
52
+
53
+ ### From PyPI (coming soon)
54
+
55
+ ```bash
56
+ pip install ppop
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ # Interactive mode (real-time monitoring)
63
+ ppop
64
+
65
+ # One-shot mode (print once and exit)
66
+ ppop --once
67
+
68
+ # Custom refresh interval (default: 2s)
69
+ ppop -i 5
70
+ ```
71
+
72
+ ### Keyboard Shortcuts (interactive mode)
73
+
74
+ | Key | Action |
75
+ |-----|--------|
76
+ | `q` | Quit |
77
+ | `↑` / `k` | Scroll up |
78
+ | `↓` / `j` | Scroll down |
79
+ | `PgUp` / `PgDn` | Scroll by page |
80
+ | `r` | Force refresh |
81
+
82
+ ## Configuration
83
+
84
+ If `ppu-smi` is not at the default path, set the environment variable:
85
+
86
+ ```bash
87
+ export PPU_SMI_PATH=/path/to/ppu-smi
88
+ ```
89
+
90
+ ## License
91
+
92
+ [MIT](LICENSE)
@@ -0,0 +1,3 @@
1
+ """popup - An interactive PPU process viewer and resource monitor."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m popup."""
2
+
3
+ from popup.cli import main
4
+
5
+ main()
@@ -0,0 +1,46 @@
1
+ """CLI entry point for popup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import curses
7
+ import sys
8
+
9
+
10
+ def parse_arguments() -> argparse.Namespace:
11
+ parser = argparse.ArgumentParser(
12
+ prog="ppop",
13
+ description="An interactive PPU process viewer and resource monitor.",
14
+ )
15
+ parser.add_argument(
16
+ "-1", "--once",
17
+ action="store_true",
18
+ help="Report device info once and exit (non-interactive).",
19
+ )
20
+ parser.add_argument(
21
+ "-i", "--interval",
22
+ type=float,
23
+ default=2.0,
24
+ help="Update interval in seconds (default: 2).",
25
+ )
26
+ return parser.parse_args()
27
+
28
+
29
+ def main() -> None:
30
+ args = parse_arguments()
31
+
32
+ from popup.tui import TUI, print_once
33
+
34
+ if args.once or not sys.stdout.isatty():
35
+ print_once()
36
+ return
37
+
38
+ tui = TUI(interval=args.interval)
39
+ try:
40
+ curses.wrapper(tui.run)
41
+ except KeyboardInterrupt:
42
+ pass
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,225 @@
1
+ """PPU device and process data collection via ppu-smi."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Optional
9
+
10
+ PPU_SMI = os.environ.get("PPU_SMI_PATH", "/usr/local/PPU_SDK/ppu-smi/bin/ppu-smi")
11
+
12
+ DEVICE_QUERY_FIELDS = (
13
+ "index,name,uuid,pci.bus_id,memory.total,memory.used,memory.free,"
14
+ "utilization.ppu,utilization.memory,temperature.ppu,"
15
+ "power.draw,power.limit,fan.speed,clocks.current.sm,clocks.current.memory,"
16
+ "persistence_mode,pstate,compute_mode,ecc.errors.corrected.volatile.total"
17
+ )
18
+
19
+ PROCESS_QUERY_FIELDS = "pid,process_name,used_ppu_memory,ppu_bus_id"
20
+
21
+
22
+ def _parse_value(raw: str) -> str:
23
+ """Strip units and whitespace from a ppu-smi CSV value."""
24
+ raw = raw.strip()
25
+ for suffix in (" MiB", " W", " C", " %", " MHz", " KB/s"):
26
+ if raw.endswith(suffix):
27
+ return raw[: -len(suffix)].strip()
28
+ return raw
29
+
30
+
31
+ def _parse_int(raw: str, default: int = 0) -> int:
32
+ v = _parse_value(raw)
33
+ if v in ("[N/A]", "N/A", "[Not Supported]", ""):
34
+ return default
35
+ try:
36
+ return int(v)
37
+ except ValueError:
38
+ return default
39
+
40
+
41
+ def _parse_float(raw: str, default: float = 0.0) -> float:
42
+ v = _parse_value(raw)
43
+ if v in ("[N/A]", "N/A", "[Not Supported]", ""):
44
+ return default
45
+ try:
46
+ return float(v)
47
+ except ValueError:
48
+ return default
49
+
50
+
51
+ def _parse_str(raw: str) -> str:
52
+ v = raw.strip()
53
+ if v in ("[N/A]", "[Not Supported]"):
54
+ return "N/A"
55
+ return v
56
+
57
+
58
+ @dataclass
59
+ class PPUDevice:
60
+ index: int = 0
61
+ name: str = ""
62
+ uuid: str = ""
63
+ bus_id: str = ""
64
+ memory_total: int = 0 # MiB
65
+ memory_used: int = 0 # MiB
66
+ memory_free: int = 0 # MiB
67
+ ppu_utilization: int = 0 # %
68
+ memory_utilization: int = 0 # %
69
+ temperature: int = 0 # C
70
+ power_draw: float = 0.0 # W
71
+ power_limit: float = 0.0 # W
72
+ fan_speed: str = "N/A"
73
+ sm_clock: int = 0 # MHz
74
+ memory_clock: int = 0 # MHz
75
+ persistence_mode: str = "N/A"
76
+ pstate: str = "N/A"
77
+ compute_mode: str = "Default"
78
+ ecc_errors: int = 0
79
+ processes: List[PPUProcess] = field(default_factory=list)
80
+
81
+ @property
82
+ def memory_percent(self) -> float:
83
+ if self.memory_total == 0:
84
+ return 0.0
85
+ return 100.0 * self.memory_used / self.memory_total
86
+
87
+ @property
88
+ def power_status(self) -> str:
89
+ return f"{self.power_draw:.0f}W / {self.power_limit:.0f}W"
90
+
91
+ @property
92
+ def memory_usage(self) -> str:
93
+ return f"{self.memory_used}MiB / {self.memory_total}MiB"
94
+
95
+
96
+ @dataclass
97
+ class PPUProcess:
98
+ pid: int = 0
99
+ name: str = ""
100
+ ppu_memory: int = 0 # MiB
101
+ bus_id: str = ""
102
+ device_index: int = -1
103
+ username: str = ""
104
+ command: str = ""
105
+ cpu_percent: float = 0.0
106
+ host_memory: float = 0.0 # %
107
+ running_time: str = ""
108
+
109
+
110
+ def _run_ppu_smi(*args: str) -> str:
111
+ try:
112
+ result = subprocess.run(
113
+ [PPU_SMI, *args],
114
+ capture_output=True,
115
+ text=True,
116
+ timeout=10,
117
+ )
118
+ return result.stdout
119
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
120
+ return ""
121
+
122
+
123
+ def _enrich_process(proc: PPUProcess) -> None:
124
+ """Add host-level info (username, cmdline, cpu%, mem%) via psutil."""
125
+ try:
126
+ import psutil
127
+
128
+ p = psutil.Process(proc.pid)
129
+ proc.username = p.username()
130
+ try:
131
+ cmdline = p.cmdline()
132
+ proc.command = " ".join(cmdline) if cmdline else proc.name
133
+ except (psutil.AccessDenied, psutil.ZombieProcess):
134
+ proc.command = proc.name
135
+ proc.cpu_percent = p.cpu_percent(interval=0)
136
+ proc.host_memory = p.memory_percent()
137
+ try:
138
+ import time
139
+ elapsed = time.time() - p.create_time()
140
+ hours, rem = divmod(int(elapsed), 3600)
141
+ minutes, seconds = divmod(rem, 60)
142
+ if hours > 0:
143
+ proc.running_time = f"{hours}:{minutes:02d}:{seconds:02d}"
144
+ else:
145
+ proc.running_time = f"{minutes}:{seconds:02d}"
146
+ except (psutil.AccessDenied, psutil.ZombieProcess):
147
+ proc.running_time = "N/A"
148
+ except Exception:
149
+ pass
150
+
151
+
152
+ def query_devices() -> List[PPUDevice]:
153
+ """Query all PPU devices and their processes."""
154
+ # Query devices
155
+ output = _run_ppu_smi(f"--query-ppu={DEVICE_QUERY_FIELDS}", "--format=csv")
156
+ if not output.strip():
157
+ return []
158
+
159
+ lines = output.strip().splitlines()
160
+ if len(lines) < 2:
161
+ return []
162
+
163
+ devices: List[PPUDevice] = []
164
+ bus_id_to_device: dict[str, PPUDevice] = {}
165
+
166
+ for line in lines[1:]:
167
+ cols = line.split(",")
168
+ if len(cols) < 19:
169
+ continue
170
+ dev = PPUDevice(
171
+ index=_parse_int(cols[0]),
172
+ name=_parse_str(cols[1]),
173
+ uuid=_parse_str(cols[2]),
174
+ bus_id=_parse_str(cols[3]),
175
+ memory_total=_parse_int(cols[4]),
176
+ memory_used=_parse_int(cols[5]),
177
+ memory_free=_parse_int(cols[6]),
178
+ ppu_utilization=_parse_int(cols[7]),
179
+ memory_utilization=_parse_int(cols[8]),
180
+ temperature=_parse_int(cols[9]),
181
+ power_draw=_parse_float(cols[10]),
182
+ power_limit=_parse_float(cols[11]),
183
+ fan_speed=_parse_str(cols[12]),
184
+ sm_clock=_parse_int(cols[13]),
185
+ memory_clock=_parse_int(cols[14]),
186
+ persistence_mode=_parse_str(cols[15]),
187
+ pstate=_parse_str(cols[16]),
188
+ compute_mode=_parse_str(cols[17]),
189
+ ecc_errors=_parse_int(cols[18]),
190
+ )
191
+ devices.append(dev)
192
+ bus_id_to_device[dev.bus_id] = dev
193
+
194
+ # Query processes
195
+ proc_output = _run_ppu_smi(
196
+ f"--query-compute-apps={PROCESS_QUERY_FIELDS}", "--format=csv"
197
+ )
198
+ if proc_output.strip():
199
+ proc_lines = proc_output.strip().splitlines()
200
+ for pline in proc_lines[1:]:
201
+ pcols = pline.split(",")
202
+ if len(pcols) < 4:
203
+ continue
204
+ bus_id = _parse_str(pcols[3])
205
+ proc = PPUProcess(
206
+ pid=_parse_int(pcols[0]),
207
+ name=_parse_str(pcols[1]),
208
+ ppu_memory=_parse_int(pcols[2]),
209
+ bus_id=bus_id,
210
+ )
211
+ _enrich_process(proc)
212
+ dev = bus_id_to_device.get(bus_id)
213
+ if dev is not None:
214
+ proc.device_index = dev.index
215
+ dev.processes.append(proc)
216
+
217
+ return devices
218
+
219
+
220
+ def get_driver_version() -> str:
221
+ output = _run_ppu_smi("--query-ppu=driver_version", "--format=csv")
222
+ lines = output.strip().splitlines()
223
+ if len(lines) >= 2:
224
+ return lines[1].strip()
225
+ return "N/A"
@@ -0,0 +1,513 @@
1
+ """Curses-based TUI for monitoring PPU devices."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import curses
6
+ import signal
7
+ import time
8
+ from dataclasses import dataclass, field
9
+ from typing import List, Optional
10
+
11
+ import psutil
12
+
13
+ from popup.device import PPUDevice, PPUProcess, get_driver_version, query_devices
14
+
15
+
16
+ @dataclass
17
+ class HostInfo:
18
+ cpu_percent: float = 0.0
19
+ cpu_count: int = 0
20
+ cpu_percents: List[float] = field(default_factory=list)
21
+ mem_total: int = 0 # bytes
22
+ mem_used: int = 0 # bytes
23
+ mem_percent: float = 0.0
24
+ swap_total: int = 0
25
+ swap_used: int = 0
26
+ swap_percent: float = 0.0
27
+ load_avg: tuple = (0.0, 0.0, 0.0)
28
+
29
+
30
+ def query_host() -> HostInfo:
31
+ info = HostInfo()
32
+ info.cpu_count = psutil.cpu_count() or 1
33
+ info.cpu_percents = psutil.cpu_percent(percpu=True) or []
34
+ info.cpu_percent = psutil.cpu_percent()
35
+ mem = psutil.virtual_memory()
36
+ info.mem_total = mem.total
37
+ info.mem_used = mem.used
38
+ info.mem_percent = mem.percent
39
+ swap = psutil.swap_memory()
40
+ info.swap_total = swap.total
41
+ info.swap_used = swap.used
42
+ info.swap_percent = swap.percent
43
+ try:
44
+ info.load_avg = psutil.getloadavg()
45
+ except (AttributeError, OSError):
46
+ pass
47
+ return info
48
+
49
+
50
+ def _fmt_bytes(n: int) -> str:
51
+ if n >= 1 << 30:
52
+ return f"{n / (1 << 30):.1f}GiB"
53
+ elif n >= 1 << 20:
54
+ return f"{n / (1 << 20):.0f}MiB"
55
+ else:
56
+ return f"{n / (1 << 10):.0f}KiB"
57
+
58
+
59
+ # Color pair IDs
60
+ PAIR_NORMAL = 0
61
+ PAIR_HEADER = 1
62
+ PAIR_GOOD = 2
63
+ PAIR_WARN = 3
64
+ PAIR_CRIT = 4
65
+ PAIR_BAR_LOW = 5
66
+ PAIR_BAR_MED = 6
67
+ PAIR_BAR_HIGH = 7
68
+ PAIR_TITLE = 8
69
+ PAIR_PROCESS = 9
70
+ PAIR_BORDER = 10
71
+ PAIR_HIGHLIGHT = 11
72
+
73
+
74
+ def _init_colors() -> None:
75
+ curses.start_color()
76
+ curses.use_default_colors()
77
+ curses.init_pair(PAIR_HEADER, curses.COLOR_WHITE, curses.COLOR_BLUE)
78
+ curses.init_pair(PAIR_GOOD, curses.COLOR_GREEN, -1)
79
+ curses.init_pair(PAIR_WARN, curses.COLOR_YELLOW, -1)
80
+ curses.init_pair(PAIR_CRIT, curses.COLOR_RED, -1)
81
+ curses.init_pair(PAIR_BAR_LOW, curses.COLOR_GREEN, -1)
82
+ curses.init_pair(PAIR_BAR_MED, curses.COLOR_YELLOW, -1)
83
+ curses.init_pair(PAIR_BAR_HIGH, curses.COLOR_RED, -1)
84
+ curses.init_pair(PAIR_TITLE, curses.COLOR_CYAN, -1)
85
+ curses.init_pair(PAIR_PROCESS, curses.COLOR_WHITE, -1)
86
+ curses.init_pair(PAIR_BORDER, curses.COLOR_WHITE, -1)
87
+ curses.init_pair(PAIR_HIGHLIGHT, curses.COLOR_BLACK, curses.COLOR_CYAN)
88
+
89
+
90
+ def _util_color(percent: float) -> int:
91
+ if percent < 50:
92
+ return curses.color_pair(PAIR_BAR_LOW) | curses.A_BOLD
93
+ elif percent < 80:
94
+ return curses.color_pair(PAIR_BAR_MED) | curses.A_BOLD
95
+ else:
96
+ return curses.color_pair(PAIR_BAR_HIGH) | curses.A_BOLD
97
+
98
+
99
+ def _temp_color(temp: int) -> int:
100
+ if temp < 50:
101
+ return curses.color_pair(PAIR_GOOD)
102
+ elif temp < 75:
103
+ return curses.color_pair(PAIR_WARN)
104
+ else:
105
+ return curses.color_pair(PAIR_CRIT)
106
+
107
+
108
+ def _draw_bar(win, y: int, x: int, width: int, percent: float, label: str = "") -> None:
109
+ """Draw a utilization bar [|||| ] with color."""
110
+ if width < 4:
111
+ return
112
+ bar_inner = width - 2 # excluding [ and ]
113
+ filled = int(bar_inner * percent / 100.0)
114
+ filled = min(filled, bar_inner)
115
+
116
+ attr = _util_color(percent)
117
+ try:
118
+ win.addstr(y, x, "[", curses.color_pair(PAIR_BORDER))
119
+ win.addstr(y, x + 1, "|" * filled, attr)
120
+ win.addstr(y, x + 1 + filled, " " * (bar_inner - filled))
121
+ win.addstr(y, x + width - 1, "]", curses.color_pair(PAIR_BORDER))
122
+ if label:
123
+ lx = x + width + 1
124
+ win.addstr(y, lx, label, attr)
125
+ except curses.error:
126
+ pass
127
+
128
+
129
+ def _safe_addstr(win, y: int, x: int, text: str, attr: int = 0) -> None:
130
+ max_y, max_x = win.getmaxyx()
131
+ if y < 0 or y >= max_y or x >= max_x:
132
+ return
133
+ available = max_x - x - 1
134
+ if available <= 0:
135
+ return
136
+ try:
137
+ win.addstr(y, x, text[:available], attr)
138
+ except curses.error:
139
+ pass
140
+
141
+
142
+ def _draw_header(win, y: int, width: int, driver_version: str) -> int:
143
+ """Draw the top header. Returns next y."""
144
+ timestamp = time.strftime("%a %b %d %H:%M:%S %Y")
145
+ title = f" ppop - PPU Monitor"
146
+ ver = f"Driver: {driver_version}"
147
+
148
+ _safe_addstr(win, y, 0, " " * width, curses.color_pair(PAIR_HEADER))
149
+ _safe_addstr(win, y, 1, title, curses.color_pair(PAIR_HEADER) | curses.A_BOLD)
150
+ _safe_addstr(win, y, width - len(ver) - 2, ver, curses.color_pair(PAIR_HEADER))
151
+ y += 1
152
+ _safe_addstr(win, y, 0, " " * width, curses.color_pair(PAIR_HEADER))
153
+ _safe_addstr(win, y, 1, f" {timestamp}", curses.color_pair(PAIR_HEADER))
154
+ help_text = "q:Quit ↑↓:Scroll"
155
+ _safe_addstr(win, y, width - len(help_text) - 2, help_text, curses.color_pair(PAIR_HEADER))
156
+ return y + 1
157
+
158
+
159
+ def _draw_device_panel(win, y: int, width: int, devices: List[PPUDevice]) -> int:
160
+ """Draw device info panel. Returns next y."""
161
+ if not devices:
162
+ _safe_addstr(win, y, 1, "No PPU devices found.", curses.color_pair(PAIR_CRIT) | curses.A_BOLD)
163
+ return y + 2
164
+
165
+ # Table header
166
+ border = "+" + "-" * (width - 2) + "+"
167
+ _safe_addstr(win, y, 0, border, curses.color_pair(PAIR_BORDER))
168
+ y += 1
169
+
170
+ col_header = (
171
+ "| PPU Name Temp Power |"
172
+ " Memory-Usage | PPU-Util |"
173
+ )
174
+ # Simplified header that fits
175
+ hdr1 = f"| {'PPU':>3} {'Name':<16} {'Temp':>5} {'Pwr:Usage/Cap':>15} | {'Memory-Usage':^22} | {'PPU-Util':^9} |"
176
+ _safe_addstr(win, y, 0, hdr1[:width], curses.color_pair(PAIR_TITLE) | curses.A_BOLD)
177
+ y += 1
178
+
179
+ sep = "|" + "-" * (width - 2) + "|"
180
+ _safe_addstr(win, y, 0, sep, curses.color_pair(PAIR_BORDER))
181
+ y += 1
182
+
183
+ for dev in devices:
184
+ # Row 1: index, name, temp, power, memory usage, ppu util
185
+ mem_usage = f"{dev.memory_used:>5}MiB / {dev.memory_total}MiB"
186
+ ppu_util = f"{dev.ppu_utilization}%"
187
+
188
+ line = f"| {dev.index:>3} {dev.name:<16} "
189
+ _safe_addstr(win, y, 0, line, curses.color_pair(PAIR_PROCESS))
190
+
191
+ # Temperature with color
192
+ temp_str = f"{dev.temperature:>3}C"
193
+ tx = len(line)
194
+ _safe_addstr(win, y, tx, temp_str, _temp_color(dev.temperature))
195
+ tx += len(temp_str)
196
+
197
+ # Power
198
+ pwr_str = f" {dev.power_draw:>6.0f}W / {dev.power_limit:.0f}W"
199
+ _safe_addstr(win, y, tx, pwr_str, curses.color_pair(PAIR_PROCESS))
200
+ tx += len(pwr_str)
201
+
202
+ _safe_addstr(win, y, tx, " | ", curses.color_pair(PAIR_BORDER))
203
+ tx += 3
204
+
205
+ # Memory usage with bar
206
+ mem_pct = dev.memory_percent
207
+ bar_width = 12
208
+ _draw_bar(win, y, tx, bar_width, mem_pct)
209
+ tx += bar_width + 1
210
+ mem_str = f"{dev.memory_used:>5}/{dev.memory_total}M"
211
+ _safe_addstr(win, y, tx, mem_str, _util_color(mem_pct))
212
+ tx += len(mem_str)
213
+
214
+ _safe_addstr(win, y, tx, " | ", curses.color_pair(PAIR_BORDER))
215
+ tx += 3
216
+
217
+ # PPU utilization with bar
218
+ _draw_bar(win, y, tx, bar_width, dev.ppu_utilization)
219
+ tx += bar_width + 1
220
+ util_str = f"{dev.ppu_utilization:>3}%"
221
+ _safe_addstr(win, y, tx, util_str, _util_color(dev.ppu_utilization))
222
+ tx += len(util_str)
223
+
224
+ _safe_addstr(win, y, tx, " |", curses.color_pair(PAIR_BORDER))
225
+ y += 1
226
+
227
+ _safe_addstr(win, y, 0, border, curses.color_pair(PAIR_BORDER))
228
+ return y + 1
229
+
230
+
231
+ def _draw_host_panel(win, y: int, width: int, host: HostInfo) -> int:
232
+ """Draw CPU and memory usage panel. Returns next y."""
233
+ border = "+" + "-" * (width - 2) + "+"
234
+ _safe_addstr(win, y, 0, border, curses.color_pair(PAIR_BORDER))
235
+ y += 1
236
+
237
+ _safe_addstr(
238
+ win, y, 0,
239
+ "| Host:" + " " * (width - 8) + "|",
240
+ curses.color_pair(PAIR_TITLE) | curses.A_BOLD,
241
+ )
242
+ y += 1
243
+
244
+ # CPU row: overall bar + per-core mini bars
245
+ bar_width = 20
246
+ cpu_label = f"CPU {host.cpu_percent:>5.1f}% ({host.cpu_count} cores)"
247
+ load_str = f"Load: {host.load_avg[0]:.2f} {host.load_avg[1]:.2f} {host.load_avg[2]:.2f}"
248
+
249
+ _safe_addstr(win, y, 1, "| ", curses.color_pair(PAIR_BORDER))
250
+ _safe_addstr(win, y, 3, "CPU ", curses.color_pair(PAIR_TITLE) | curses.A_BOLD)
251
+ _draw_bar(win, y, 7, bar_width, host.cpu_percent)
252
+ pct_str = f" {host.cpu_percent:>5.1f}% ({host.cpu_count} cores)"
253
+ _safe_addstr(win, y, 7 + bar_width + 1, pct_str, _util_color(host.cpu_percent))
254
+
255
+ # Memory on the same row, right-aligned area
256
+ mem_x = max(7 + bar_width + 1 + len(pct_str) + 3, width // 2)
257
+ _safe_addstr(win, y, mem_x, "MEM ", curses.color_pair(PAIR_TITLE) | curses.A_BOLD)
258
+ _draw_bar(win, y, mem_x + 4, bar_width, host.mem_percent)
259
+ mem_str = f" {_fmt_bytes(host.mem_used)}/{_fmt_bytes(host.mem_total)} ({host.mem_percent:.1f}%)"
260
+ _safe_addstr(win, y, mem_x + 4 + bar_width + 1, mem_str, _util_color(host.mem_percent))
261
+ _safe_addstr(win, y, width - 1, "|", curses.color_pair(PAIR_BORDER))
262
+ y += 1
263
+
264
+ # Second row: per-core CPU mini view + swap
265
+ _safe_addstr(win, y, 1, "| ", curses.color_pair(PAIR_BORDER))
266
+ _safe_addstr(win, y, 3, "Per-core: ", curses.color_pair(PAIR_PROCESS))
267
+ cx = 13
268
+ cores_per_row = (width - 16) // 5 # each core ~5 chars "XX% "
269
+ for i, cpct in enumerate(host.cpu_percents):
270
+ if cx + 5 >= mem_x - 1:
271
+ break
272
+ core_str = f"{cpct:>3.0f}%"
273
+ _safe_addstr(win, y, cx, core_str, _util_color(cpct))
274
+ cx += 5
275
+
276
+ # Swap on the right
277
+ _safe_addstr(win, y, mem_x, "SWP ", curses.color_pair(PAIR_TITLE) | curses.A_BOLD)
278
+ _draw_bar(win, y, mem_x + 4, bar_width, host.swap_percent)
279
+ swap_str = f" {_fmt_bytes(host.swap_used)}/{_fmt_bytes(host.swap_total)} ({host.swap_percent:.1f}%)"
280
+ _safe_addstr(win, y, mem_x + 4 + bar_width + 1, swap_str, _util_color(host.swap_percent))
281
+
282
+ _safe_addstr(win, y, width - 1, "|", curses.color_pair(PAIR_BORDER))
283
+ y += 1
284
+
285
+ # Third row: load average
286
+ _safe_addstr(win, y, 1, "| ", curses.color_pair(PAIR_BORDER))
287
+ _safe_addstr(win, y, 3, f"Load Avg: {host.load_avg[0]:.2f} {host.load_avg[1]:.2f} {host.load_avg[2]:.2f} (1/5/15 min)", curses.color_pair(PAIR_PROCESS))
288
+ _safe_addstr(win, y, width - 1, "|", curses.color_pair(PAIR_BORDER))
289
+ y += 1
290
+
291
+ _safe_addstr(win, y, 0, border, curses.color_pair(PAIR_BORDER))
292
+ return y + 1
293
+
294
+
295
+ def _draw_process_panel(
296
+ win, y: int, width: int, devices: List[PPUDevice], scroll_offset: int
297
+ ) -> int:
298
+ """Draw processes table. Returns next y."""
299
+ border = "+" + "-" * (width - 2) + "+"
300
+ _safe_addstr(win, y, 0, border, curses.color_pair(PAIR_BORDER))
301
+ y += 1
302
+
303
+ _safe_addstr(
304
+ win, y, 0,
305
+ "| Processes:" + " " * (width - 13) + "|",
306
+ curses.color_pair(PAIR_TITLE) | curses.A_BOLD,
307
+ )
308
+ y += 1
309
+
310
+ # Column header
311
+ hdr = (
312
+ f"| {'PPU':>3} {'PID':>8} {'USER':<10} "
313
+ f"{'PPU-MEM':>8} {'PPU%':>5} {'MEM%':>5} "
314
+ f"{'CPU%':>5} {'TIME':>9} {'Command':<20} |"
315
+ )
316
+ _safe_addstr(win, y, 0, hdr[:width], curses.color_pair(PAIR_TITLE) | curses.A_BOLD)
317
+ y += 1
318
+
319
+ sep = "|" + "-" * (width - 2) + "|"
320
+ _safe_addstr(win, y, 0, sep, curses.color_pair(PAIR_BORDER))
321
+ y += 1
322
+
323
+ # Collect all processes
324
+ all_procs: list[PPUProcess] = []
325
+ for dev in devices:
326
+ all_procs.extend(dev.processes)
327
+
328
+ max_y, _ = win.getmaxyx()
329
+ available_rows = max_y - y - 2 # leave room for bottom border + status
330
+
331
+ if not all_procs:
332
+ _safe_addstr(win, y, 0, f"| {'No running processes':^{width-4}} |", curses.color_pair(PAIR_PROCESS))
333
+ y += 1
334
+ else:
335
+ visible = all_procs[scroll_offset : scroll_offset + max(available_rows, 1)]
336
+ for proc in visible:
337
+ if y >= max_y - 2:
338
+ break
339
+ user = (proc.username[:10] if proc.username else "N/A")
340
+ cmd = proc.command if proc.command else proc.name
341
+ # Truncate command to fit
342
+ cmd_width = max(width - 68, 10)
343
+ if len(cmd) > cmd_width:
344
+ cmd = cmd[:cmd_width - 3] + "..."
345
+
346
+ line = (
347
+ f"| {proc.device_index:>3} {proc.pid:>8} {user:<10} "
348
+ f"{proc.ppu_memory:>6}MiB {'-':>5} {proc.host_memory:>4.1f}% "
349
+ f"{proc.cpu_percent:>4.1f}% {proc.running_time:>9} {cmd:<{cmd_width}} |"
350
+ )
351
+ _safe_addstr(win, y, 0, line[:width], curses.color_pair(PAIR_PROCESS))
352
+ y += 1
353
+
354
+ _safe_addstr(win, y, 0, border, curses.color_pair(PAIR_BORDER))
355
+ y += 1
356
+
357
+ # Show scroll info
358
+ total = len(all_procs)
359
+ if total > available_rows:
360
+ info = f" [{scroll_offset+1}-{min(scroll_offset+available_rows, total)}/{total}] "
361
+ _safe_addstr(win, y, 1, info, curses.color_pair(PAIR_TITLE))
362
+
363
+ return y
364
+
365
+
366
+ class TUI:
367
+ def __init__(self, interval: float = 2.0):
368
+ self.interval = interval
369
+ self.devices: List[PPUDevice] = []
370
+ self.host: HostInfo = HostInfo()
371
+ self.driver_version = "N/A"
372
+ self.scroll_offset = 0
373
+ self.running = True
374
+
375
+ def _refresh_data(self) -> None:
376
+ self.devices = query_devices()
377
+ self.host = query_host()
378
+ if self.driver_version == "N/A":
379
+ self.driver_version = get_driver_version()
380
+
381
+ def _total_processes(self) -> int:
382
+ return sum(len(d.processes) for d in self.devices)
383
+
384
+ def run(self, stdscr) -> None:
385
+ _init_colors()
386
+ curses.curs_set(0)
387
+ stdscr.timeout(200) # 200ms for responsive input
388
+ curses.halfdelay(1)
389
+
390
+ self._refresh_data()
391
+ last_refresh = time.time()
392
+
393
+ while self.running:
394
+ now = time.time()
395
+ if now - last_refresh >= self.interval:
396
+ self._refresh_data()
397
+ last_refresh = now
398
+
399
+ stdscr.erase()
400
+ height, width = stdscr.getmaxyx()
401
+ if width < 40 or height < 10:
402
+ _safe_addstr(stdscr, 0, 0, "Terminal too small!", curses.A_BOLD)
403
+ stdscr.refresh()
404
+ key = stdscr.getch()
405
+ if key == ord("q") or key == ord("Q"):
406
+ break
407
+ continue
408
+
409
+ y = 0
410
+ y = _draw_header(stdscr, y, width, self.driver_version)
411
+ y += 1
412
+ y = _draw_device_panel(stdscr, y, width, self.devices)
413
+ y += 1
414
+ y = _draw_host_panel(stdscr, y, width, self.host)
415
+ y += 1
416
+ y = _draw_process_panel(stdscr, y, width, self.devices, self.scroll_offset)
417
+
418
+ stdscr.refresh()
419
+
420
+ # Handle input
421
+ key = stdscr.getch()
422
+ if key == ord("q") or key == ord("Q"):
423
+ break
424
+ elif key == curses.KEY_DOWN or key == ord("j"):
425
+ total = self._total_processes()
426
+ if self.scroll_offset < total - 1:
427
+ self.scroll_offset += 1
428
+ elif key == curses.KEY_UP or key == ord("k"):
429
+ if self.scroll_offset > 0:
430
+ self.scroll_offset -= 1
431
+ elif key == curses.KEY_PPAGE:
432
+ self.scroll_offset = max(0, self.scroll_offset - 10)
433
+ elif key == curses.KEY_NPAGE:
434
+ total = self._total_processes()
435
+ self.scroll_offset = min(total - 1, self.scroll_offset + 10)
436
+ elif key == ord("r") or key == ord("R"):
437
+ self._refresh_data()
438
+ last_refresh = time.time()
439
+
440
+
441
+ def print_once() -> None:
442
+ """Print a one-shot snapshot to stdout (non-interactive mode)."""
443
+ devices = query_devices()
444
+ driver_version = get_driver_version()
445
+
446
+ timestamp = time.strftime("%a %b %d %H:%M:%S %Y")
447
+ print(timestamp)
448
+ print("+" + "-" * 79 + "+")
449
+ print(f"| {'ppop - PPU Monitor':<49} Driver Version: {driver_version:<12} |")
450
+ print("+" + "-" * 35 + "+" + "-" * 22 + "+" + "-" * 19 + "+")
451
+ print(
452
+ f"| {'PPU Name':^35}| {'Memory-Usage':^22}| {'PPU-Util':^19}|"
453
+ )
454
+ print("+" + "=" * 35 + "+" + "=" * 22 + "+" + "=" * 19 + "+")
455
+
456
+ for dev in devices:
457
+ name_part = f"{dev.index} {dev.name} {dev.temperature}C {dev.power_status}"
458
+ mem_part = f"{dev.memory_used}MiB / {dev.memory_total}MiB"
459
+ util_part = f"{dev.ppu_utilization}% {dev.compute_mode}"
460
+ print(f"| {name_part:<35}| {mem_part:>20} | {util_part:>17} |")
461
+
462
+ print("+" + "-" * 35 + "+" + "-" * 22 + "+" + "-" * 19 + "+")
463
+ print()
464
+
465
+ # Host info
466
+ host = query_host()
467
+ print("+" + "-" * 79 + "+")
468
+ print(f"| {'Host:':<79}|")
469
+ cpu_bar_filled = int(20 * host.cpu_percent / 100)
470
+ cpu_bar = "|" * cpu_bar_filled + " " * (20 - cpu_bar_filled)
471
+ mem_bar_filled = int(20 * host.mem_percent / 100)
472
+ mem_bar = "|" * mem_bar_filled + " " * (20 - mem_bar_filled)
473
+ print(
474
+ f"| CPU [{cpu_bar}] {host.cpu_percent:>5.1f}% ({host.cpu_count} cores)"
475
+ f" MEM [{mem_bar}] {_fmt_bytes(host.mem_used)}/{_fmt_bytes(host.mem_total)} ({host.mem_percent:.1f}%)"
476
+ )
477
+ swap_bar_filled = int(20 * host.swap_percent / 100)
478
+ swap_bar = "|" * swap_bar_filled + " " * (20 - swap_bar_filled)
479
+ print(
480
+ f"| Load: {host.load_avg[0]:.2f} {host.load_avg[1]:.2f} {host.load_avg[2]:.2f} (1/5/15 min)"
481
+ f" SWP [{swap_bar}] {_fmt_bytes(host.swap_used)}/{_fmt_bytes(host.swap_total)} ({host.swap_percent:.1f}%)"
482
+ )
483
+ print("+" + "-" * 79 + "+")
484
+ print()
485
+
486
+ # Processes
487
+ print("+" + "-" * 79 + "+")
488
+ print(f"| {'Processes:':<79}|")
489
+ print(
490
+ f"| {'PPU':>3} {'PID':>8} {'USER':<10} "
491
+ f"{'PPU-MEM':>8} {'CPU%':>5} {'MEM%':>5} "
492
+ f"{'TIME':>9} {'Command':<15} |"
493
+ )
494
+ print("+" + "=" * 79 + "+")
495
+
496
+ has_proc = False
497
+ for dev in devices:
498
+ for proc in dev.processes:
499
+ has_proc = True
500
+ user = (proc.username[:10] if proc.username else "N/A")
501
+ cmd = proc.command if proc.command else proc.name
502
+ if len(cmd) > 15:
503
+ cmd = cmd[:12] + "..."
504
+ print(
505
+ f"| {proc.device_index:>3} {proc.pid:>8} {user:<10} "
506
+ f"{proc.ppu_memory:>6}MiB {proc.cpu_percent:>4.1f}% "
507
+ f"{proc.host_memory:>4.1f}% {proc.running_time:>9} {cmd:<15} |"
508
+ )
509
+
510
+ if not has_proc:
511
+ print(f"| {'No running processes':^79}|")
512
+
513
+ print("+" + "-" * 79 + "+")
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: ppop
3
+ Version: 0.1.0
4
+ Summary: An interactive PPU process viewer and resource monitor, inspired by nvitop.
5
+ Author-email: Zhongwang Lun <lunandcnn@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: ppu,monitor,process-viewer,tui
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console :: Curses
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: System :: Monitoring
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: psutil>=5.6.6
18
+ Dynamic: license-file
19
+
20
+ # ppop
21
+
22
+ An interactive PPU process viewer and resource monitor for the terminal, inspired by [nvitop](https://github.com/XuehaiPan/nvitop).
23
+
24
+ ## Features
25
+
26
+ - Real-time PPU device monitoring (utilization, memory, temperature, power)
27
+ - Host CPU and memory usage display
28
+ - Per-process PPU memory tracking
29
+ - Interactive curses-based TUI with color-coded utilization bars
30
+ - Non-interactive one-shot mode for scripts and logging
31
+
32
+ ## Demo
33
+
34
+ ```
35
+ +-------------------------------------------------------------------------------+
36
+ | ppop - PPU Monitor Driver Version: 1.5.5-7a3ae6 |
37
+ +-----------------------------------+----------------------+-------------------+
38
+ | PPU Name | Memory-Usage | PPU-Util |
39
+ +===================================+======================+===================+
40
+ | 0 PPU-ZW810E 32C 71W / 400W | 2MiB / 98304MiB | 0% Default |
41
+ | 1 PPU-ZW810E 32C 75W / 400W | 2MiB / 98304MiB | 0% Default |
42
+ +-----------------------------------+----------------------+-------------------+
43
+
44
+ +-------------------------------------------------------------------------------+
45
+ | Host: |
46
+ | CPU [|||| ] 21.3% (32 cores) MEM [|||||| ] 32.1GiB/64.0GiB (50.2%)
47
+ +-------------------------------------------------------------------------------+
48
+
49
+ +-------------------------------------------------------------------------------+
50
+ | Processes: |
51
+ | PPU PID USER PPU-MEM CPU% MEM% TIME Command |
52
+ +===============================================================================+
53
+ | 0 12345 user 8192MiB 3.2% 12.6% 1:23 python train.py |
54
+ +-------------------------------------------------------------------------------+
55
+ ```
56
+
57
+ ## Requirements
58
+
59
+ - Python 3.8+
60
+ - PPU device with `ppu-smi` installed (default path: `/usr/local/PPU_SDK/ppu-smi/bin/ppu-smi`)
61
+
62
+ ## Installation
63
+
64
+ ### From source
65
+
66
+ ```bash
67
+ git clone https://github.com/your-username/ppop.git
68
+ cd ppop
69
+ pip install .
70
+ ```
71
+
72
+ ### From PyPI (coming soon)
73
+
74
+ ```bash
75
+ pip install ppop
76
+ ```
77
+
78
+ ## Usage
79
+
80
+ ```bash
81
+ # Interactive mode (real-time monitoring)
82
+ ppop
83
+
84
+ # One-shot mode (print once and exit)
85
+ ppop --once
86
+
87
+ # Custom refresh interval (default: 2s)
88
+ ppop -i 5
89
+ ```
90
+
91
+ ### Keyboard Shortcuts (interactive mode)
92
+
93
+ | Key | Action |
94
+ |-----|--------|
95
+ | `q` | Quit |
96
+ | `↑` / `k` | Scroll up |
97
+ | `↓` / `j` | Scroll down |
98
+ | `PgUp` / `PgDn` | Scroll by page |
99
+ | `r` | Force refresh |
100
+
101
+ ## Configuration
102
+
103
+ If `ppu-smi` is not at the default path, set the environment variable:
104
+
105
+ ```bash
106
+ export PPU_SMI_PATH=/path/to/ppu-smi
107
+ ```
108
+
109
+ ## License
110
+
111
+ [MIT](LICENSE)
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ popup/__init__.py
5
+ popup/__main__.py
6
+ popup/cli.py
7
+ popup/device.py
8
+ popup/tui.py
9
+ ppop.egg-info/PKG-INFO
10
+ ppop.egg-info/SOURCES.txt
11
+ ppop.egg-info/dependency_links.txt
12
+ ppop.egg-info/entry_points.txt
13
+ ppop.egg-info/requires.txt
14
+ ppop.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ppop = popup.cli:main
@@ -0,0 +1 @@
1
+ psutil>=5.6.6
@@ -0,0 +1 @@
1
+ popup
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ppop"
7
+ version = "0.1.0"
8
+ description = "An interactive PPU process viewer and resource monitor, inspired by nvitop."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Zhongwang Lun", email = "lunandcnn@gmail.com" },
14
+ ]
15
+ keywords = ["ppu", "monitor", "process-viewer", "tui"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console :: Curses",
19
+ "Intended Audience :: Developers",
20
+ "Intended Audience :: System Administrators",
21
+ "Programming Language :: Python :: 3",
22
+ "Topic :: System :: Monitoring",
23
+ ]
24
+ dependencies = [
25
+ "psutil>=5.6.6",
26
+ ]
27
+
28
+ [project.scripts]
29
+ ppop = "popup.cli:main"
30
+
31
+ [tool.setuptools.packages.find]
32
+ include = ["popup*"]
ppop-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+