-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlimits.py
More file actions
300 lines (269 loc) · 9.22 KB
/
limits.py
File metadata and controls
300 lines (269 loc) · 9.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
import os
import shutil
import sys
import humanize
import psutil
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container
from textual.widgets import DataTable, Footer, Header
# The 'resource' module is POSIX-specific (Linux, macOS) and not available on Windows.
# We'll check for its availability and handle it gracefully.
try:
import resource
RESOURCE_AVAILABLE = True
except ImportError:
RESOURCE_AVAILABLE = False
def get_os_info() -> list[tuple]:
"""
Gathers various OS and filesystem configurations.
Returns:
A list of tuples, where each tuple contains the configuration name,
its value, and a description.
"""
info = [("", "CPU", "")]
# --- Section: CPU Information ---
physical_cores = psutil.cpu_count(logical=False)
logical_cores = psutil.cpu_count(logical=True)
info.append(
("CPU Physical Cores", str(physical_cores), "Number of physical CPU cores.")
)
info.append(
(
"CPU Logical Processors",
str(logical_cores),
"Total number of CPU threads (hyper-threading).",
)
)
# --- Section: Memory Information ---
info.append(("", "Memory Information", "")) # Section header
virtual_mem = psutil.virtual_memory()
info.append(
(
"Total RAM",
humanize.naturalsize(virtual_mem.total, binary=True),
"Total physical memory (RAM).",
)
)
info.append(
(
"Available RAM",
humanize.naturalsize(virtual_mem.available, binary=True),
"Memory available for new processes without swapping.",
)
)
swap_mem = psutil.swap_memory()
info.append(
(
"Total Swap",
humanize.naturalsize(swap_mem.total, binary=True),
"Total swap space available on disk.",
)
)
# --- Section: Process Resource Limits (POSIX-specific) ---
info.append(("", "Process Resource Limits", "")) # Section header
if RESOURCE_AVAILABLE:
# Helper to format resource limits, which can be -1 for "unlimited"
def format_limit(value, formatter=None):
if value in [-1, resource.RLIM_INFINITY]:
return "Unlimited"
return formatter(value) if formatter else f"{value:,}"
# Max Open Files per process
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
info.append(
(
"Max Open Files (Soft)",
format_limit(soft),
"The effective maximum number of open file descriptors per process.",
)
)
info.append(
(
"Max Open Files (Hard)",
format_limit(hard),
"The absolute upper bound for the soft limit, set by the root user.",
)
)
# Stack Size per process
soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
info.append(
(
"Stack Size (Soft)",
format_limit(soft, lambda v: humanize.naturalsize(v, binary=True)),
"The effective maximum process stack size.",
)
)
info.append(
(
"Stack Size (Hard)",
format_limit(hard, lambda v: humanize.naturalsize(v, binary=True)),
"The absolute upper bound for the stack size.",
)
)
# Max Processes per user
soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
info.append(
(
"Max Processes (Soft)",
format_limit(soft),
"The effective maximum number of processes a user can create.",
)
)
# Virtual Memory (Address Space) Limit
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
info.append(
(
"Virtual Memory (Soft)",
format_limit(soft, lambda v: humanize.naturalsize(v, binary=True)),
"The effective max virtual memory (address space) a process can use.",
)
)
# CPU Time Limit
soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
info.append(
(
"CPU Time (Soft)",
format_limit(soft, humanize.naturaldelta),
"The max CPU time a process can consume before being sent a signal.",
)
)
else:
info.append(
(
"Resource Limits",
"Not Available",
"The 'resource' module is not available on this OS (e.g., Windows).",
)
)
# --- Section: Filesystem Limits ---
info.append(("", "Filesystem Limits", "")) # Section header
path = "/" if sys.platform != "win32" else "C:\\"
try:
max_filename = os.pathconf(path, "PC_NAME_MAX")
info.append(
(
"Max Filename Length",
f"{max_filename} characters",
f"For the filesystem at '{path}'.",
)
)
except (OSError, AttributeError):
info.append(
(
"Max Filename Length",
"Not Available",
"Could not be determined for this filesystem.",
)
)
try:
max_path = os.pathconf(path, "PC_PATH_MAX")
info.append(
(
"Max Path Length",
f"{max_path} characters",
f"For the filesystem at '{path}'.",
)
)
except (OSError, AttributeError):
info.append(
(
"Max Path Length",
"Not Available",
"Could not be determined for this filesystem.",
)
)
# --- Section: Disk and Inode Information ---
info.append(("", "Mounted Filesystems", "")) # Section header
processed_devices = set()
for part in psutil.disk_partitions():
if (
"loop" in part.device
or "squashfs" in part.fstype
or not os.path.exists(part.mountpoint)
):
continue
if part.device in processed_devices:
continue
processed_devices.add(part.device)
try:
usage = shutil.disk_usage(part.mountpoint)
info.append(
(
f"Disk: {part.mountpoint}",
f"{humanize.naturalsize(usage.total)} Total, {humanize.naturalsize(usage.free)} Free",
f"Device: {part.device}",
)
)
except OSError as e:
info.append(
(
f"Disk: {part.mountpoint}",
f"Error: {e.strerror}",
f"Device: {part.device}",
)
)
if hasattr(os, "statvfs"):
try:
stats = os.statvfs(part.mountpoint)
total_inodes = stats.f_files
free_inodes = stats.f_ffree
if total_inodes > 0:
info.append(
(
f"Inodes: {part.mountpoint}",
f"{humanize.intcomma(total_inodes)} Total, {humanize.intcomma(free_inodes)} Free",
f"Filesystem type: {part.fstype}",
)
)
except OSError as e:
info.append(
(
f"Inodes: {part.mountpoint}",
f"Error: {e.strerror}",
f"Filesystem type: {part.fstype}",
)
)
return info
class LimitsApp(App):
"""A Textual application to display OS configurations."""
TITLE = "OS Limits Viewer"
SUB_TITLE = "Displays key OS and filesystem limits"
CSS_PATH = "limits.css"
BINDINGS = [
Binding("q", "quit", "Quit"),
Binding("r", "refresh", "Refresh"),
]
def compose(self) -> ComposeResult:
"""Create child widgets for the app."""
yield Header()
with Container():
yield DataTable(id="os_info_table")
yield Footer()
def on_mount(self) -> None:
"""Called when the app is mounted to populate the table."""
self.populate_table()
def action_refresh(self) -> None:
"""Called when the user presses the 'r' key to refresh data."""
self.populate_table()
def populate_table(self) -> None:
"""Gathers data and populates the DataTable widget."""
table = self.query_one(DataTable)
table.clear() # Clear previous data on refresh
if not table.columns:
table.add_columns("Configuration", "Value", "Description")
table.cursor_type = "row"
table.zebra_stripes = True
os_info = get_os_info()
for item in os_info:
if item[1] in (
"CPU",
"Memory Information", # <-- Add new section header
"Process Resource Limits",
"Filesystem Limits",
"Mounted Filesystems",
):
table.add_row()
table.add_row(*item, label=item[0])
if __name__ == "__main__":
app = LimitsApp()
app.run()