memfo 2.0.0__tar.gz → 2.0.2__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.
- {memfo-2.0.0 → memfo-2.0.2}/.gitignore +1 -0
- {memfo-2.0.0 → memfo-2.0.2}/PKG-INFO +12 -8
- {memfo-2.0.0 → memfo-2.0.2}/README.md +8 -6
- {memfo-2.0.0 → memfo-2.0.2}/memfo/main.py +76 -38
- {memfo-2.0.0 → memfo-2.0.2}/pyproject.toml +2 -2
- memfo-2.0.2/runner +19 -0
- memfo-2.0.2/runner.py +9 -0
- {memfo-2.0.0 → memfo-2.0.2}/LICENSE +0 -0
- {memfo-2.0.0 → memfo-2.0.2}/memfo/TimeMemory.py +0 -0
- {memfo-2.0.0 → memfo-2.0.2}/memfo/__init__.py +0 -0
- {memfo-2.0.0 → memfo-2.0.2}/memfo/dumper.py +0 -0
- {memfo-2.0.0 → memfo-2.0.2}/memfo/memfod.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: memfo
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Display Tool for /proc/meminfo
|
|
5
5
|
Keywords: meminfo,/proc/meminfo,RAM,memory statistics
|
|
6
6
|
Author-email: Joe Defen <joedef@duck.com>
|
|
@@ -9,7 +9,8 @@ Description-Content-Type: text/markdown
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
-
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: console-window == 3.1.2
|
|
13
14
|
Project-URL: Bug Tracker, https://github.com/joedefen/memfo/issues
|
|
14
15
|
Project-URL: Homepage, https://github.com/joedefen/memfo
|
|
15
16
|
|
|
@@ -20,15 +21,16 @@ Project-URL: Homepage, https://github.com/joedefen/memfo
|
|
|
20
21
|
|
|
21
22
|
# memfo: Memory Footprint Observer
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
An efficient, real-time Linux memory monitoring tool built in Python.
|
|
24
25
|
|
|
25
|
-
memfo provides a low-overhead, stable, and highly
|
|
26
|
+
`memfo` provides a low-overhead, stable, and highly customizable view of your system's `/proc/meminfo` data, designed for both short-term and long-term monitoring and performance debugging.
|
|
26
27
|
|
|
27
28
|
## Features at a Glance
|
|
28
29
|
|
|
29
|
-
`memfo` is a viewer for `/proc/meminfo` that shows
|
|
30
|
+
`memfo` is a viewer for `/proc/meminfo` that shows its data:
|
|
30
31
|
* as a continuously updated display of current and past values,
|
|
31
|
-
* with numbers represented in chosen units,
|
|
32
|
+
* with numbers represented in chosen units as absolute or delta values,
|
|
33
|
+
* in chosen time intervals,
|
|
32
34
|
* with selected fields at the top in the frozen section of the display,
|
|
33
35
|
* with the ability to hide certain fields, and
|
|
34
36
|
* more.
|
|
@@ -44,7 +46,7 @@ Additionally, `memfod` is a utility script that starts `memfo` in a `tmux` sessi
|
|
|
44
46
|
|
|
45
47
|
## Example memfo Output
|
|
46
48
|
```
|
|
47
|
-
[u]nits:MiB [i]tvl=15s [d]eltas:off zeros=off Dump [e]dit ?=help
|
|
49
|
+
[u]nits:MiB [i]tvl=15s [d]eltas:off zeros=off Dump [c]lock [e]dit ?=help
|
|
48
50
|
0s 15s 30s 35s 10/20 22:44:08
|
|
49
51
|
7,813.4 7,813.4 7,813.4 7,813.4 MemTotal
|
|
50
52
|
1,344.1 1,318.9 1,306.6 1,335.2 MemAvailable
|
|
@@ -66,6 +68,7 @@ Interaction keys:
|
|
|
66
68
|
* `z` - Zeros control. Toggles display of stat lines where the value is zero (helps focus on active metrics).
|
|
67
69
|
* `e` - Edit Mode. Allows nailing to top or hiding specific memory fields.
|
|
68
70
|
* `D` - Dump History. Exports all historical samples to `/tmp/memfo.csv` for analysis.
|
|
71
|
+
* `c` - clock mode. Shows clock as monotonic time since start of run, wall clock, or both.
|
|
69
72
|
* `q` - Quit. Exits the program.
|
|
70
73
|
* `?` - Help. Displays the help text.
|
|
71
74
|
* `<` `>` - Horizontally, shift columns left/right by 1.
|
|
@@ -107,3 +110,4 @@ Normally, just use `memfod` but it can take one command argument:
|
|
|
107
110
|
* `attach` - attach to the `memfo` running the special tmux session, starting if not running.
|
|
108
111
|
|
|
109
112
|
You could use `start` to arrange for `memfod` to start on reboot using `cron` if desired.
|
|
113
|
+
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
# memfo: Memory Footprint Observer
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
An efficient, real-time Linux memory monitoring tool built in Python.
|
|
9
9
|
|
|
10
|
-
memfo provides a low-overhead, stable, and highly
|
|
10
|
+
`memfo` provides a low-overhead, stable, and highly customizable view of your system's `/proc/meminfo` data, designed for both short-term and long-term monitoring and performance debugging.
|
|
11
11
|
|
|
12
12
|
## Features at a Glance
|
|
13
13
|
|
|
14
|
-
`memfo` is a viewer for `/proc/meminfo` that shows
|
|
14
|
+
`memfo` is a viewer for `/proc/meminfo` that shows its data:
|
|
15
15
|
* as a continuously updated display of current and past values,
|
|
16
|
-
* with numbers represented in chosen units,
|
|
16
|
+
* with numbers represented in chosen units as absolute or delta values,
|
|
17
|
+
* in chosen time intervals,
|
|
17
18
|
* with selected fields at the top in the frozen section of the display,
|
|
18
19
|
* with the ability to hide certain fields, and
|
|
19
20
|
* more.
|
|
@@ -29,7 +30,7 @@ Additionally, `memfod` is a utility script that starts `memfo` in a `tmux` sessi
|
|
|
29
30
|
|
|
30
31
|
## Example memfo Output
|
|
31
32
|
```
|
|
32
|
-
[u]nits:MiB [i]tvl=15s [d]eltas:off zeros=off Dump [e]dit ?=help
|
|
33
|
+
[u]nits:MiB [i]tvl=15s [d]eltas:off zeros=off Dump [c]lock [e]dit ?=help
|
|
33
34
|
0s 15s 30s 35s 10/20 22:44:08
|
|
34
35
|
7,813.4 7,813.4 7,813.4 7,813.4 MemTotal
|
|
35
36
|
1,344.1 1,318.9 1,306.6 1,335.2 MemAvailable
|
|
@@ -51,6 +52,7 @@ Interaction keys:
|
|
|
51
52
|
* `z` - Zeros control. Toggles display of stat lines where the value is zero (helps focus on active metrics).
|
|
52
53
|
* `e` - Edit Mode. Allows nailing to top or hiding specific memory fields.
|
|
53
54
|
* `D` - Dump History. Exports all historical samples to `/tmp/memfo.csv` for analysis.
|
|
55
|
+
* `c` - clock mode. Shows clock as monotonic time since start of run, wall clock, or both.
|
|
54
56
|
* `q` - Quit. Exits the program.
|
|
55
57
|
* `?` - Help. Displays the help text.
|
|
56
58
|
* `<` `>` - Horizontally, shift columns left/right by 1.
|
|
@@ -91,4 +93,4 @@ Normally, just use `memfod` but it can take one command argument:
|
|
|
91
93
|
* `status` - show whether `memfo` is running the special tmux session
|
|
92
94
|
* `attach` - attach to the `memfo` running the special tmux session, starting if not running.
|
|
93
95
|
|
|
94
|
-
You could use `start` to arrange for `memfod` to start on reboot using `cron` if desired.
|
|
96
|
+
You could use `start` to arrange for `memfod` to start on reboot using `cron` if desired.
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
"""
|
|
4
4
|
memfo is a viewer for /proc/meminfo
|
|
5
5
|
TODO:
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
6
|
+
- wall clock feature
|
|
7
|
+
- store both monotime and wall clock time with samples
|
|
8
|
+
- show (with spinner):
|
|
9
|
+
- monotime (default, as now)
|
|
10
|
+
- wall clock (two lines, "Mon 11/02" "09:30:26")
|
|
11
|
+
- both (three lines)
|
|
10
12
|
"""
|
|
11
13
|
# pylint: disable=invalid-name,global-statement
|
|
12
14
|
# pylint: disable=import-outside-toplevel,consider-using-with
|
|
@@ -26,6 +28,7 @@ import curses
|
|
|
26
28
|
import shutil
|
|
27
29
|
import subprocess
|
|
28
30
|
import signal
|
|
31
|
+
import math
|
|
29
32
|
from datetime import datetime
|
|
30
33
|
from types import SimpleNamespace
|
|
31
34
|
from console_window import ConsoleWindow , OptionSpinner
|
|
@@ -151,6 +154,7 @@ class MemFo:
|
|
|
151
154
|
zeros=args.zeros,
|
|
152
155
|
units=args.units,
|
|
153
156
|
delta=args.show_deltas,
|
|
157
|
+
clock='mono',
|
|
154
158
|
report_interval=args.report_interval,
|
|
155
159
|
dump_report=False,
|
|
156
160
|
force_compression=False, # undocumented
|
|
@@ -184,7 +188,8 @@ class MemFo:
|
|
|
184
188
|
|
|
185
189
|
self.term_width = 0 # how wide is the terminal
|
|
186
190
|
self.key_width = None
|
|
187
|
-
self.data_width = None
|
|
191
|
+
self.data_width = None # based on units
|
|
192
|
+
self.slice_width = None # based on units and clock mode
|
|
188
193
|
self.report_rows = None # the stuff to display
|
|
189
194
|
|
|
190
195
|
self.message = ''
|
|
@@ -197,7 +202,8 @@ class MemFo:
|
|
|
197
202
|
self.edit_cnt = 0 # number of pending edits
|
|
198
203
|
self.config = None
|
|
199
204
|
self.config_file = None
|
|
200
|
-
self.
|
|
205
|
+
self.dead_width = None
|
|
206
|
+
self._set_widths()
|
|
201
207
|
self.init_config()
|
|
202
208
|
|
|
203
209
|
def start_curses(self, line_cnt=200):
|
|
@@ -213,6 +219,8 @@ class MemFo:
|
|
|
213
219
|
vals=[False, True], obj=self.opts)
|
|
214
220
|
self.spin.add_key('zeros', 'z - show all zeros lines',
|
|
215
221
|
vals=[False, True], obj=self.opts)
|
|
222
|
+
self.spin.add_key('clock', 'c - time style',
|
|
223
|
+
vals=['mono', 'wall', 'both'], obj=self.opts)
|
|
216
224
|
self.spin.add_key('dump_report', 'D - dump history stats to /tmp/memfo.csv',
|
|
217
225
|
vals=[False, True], obj=self.opts)
|
|
218
226
|
self.spin.add_key('edit_mode', 'e - edit mode', vals=[False, True],
|
|
@@ -266,7 +274,7 @@ class MemFo:
|
|
|
266
274
|
if self.win:
|
|
267
275
|
self.win.stop_curses()
|
|
268
276
|
|
|
269
|
-
def
|
|
277
|
+
def _set_widths(self):
|
|
270
278
|
units, self.precision = self.opts.units, 1
|
|
271
279
|
if units == 'MB':
|
|
272
280
|
self.divisor = 1000*1000
|
|
@@ -282,8 +290,13 @@ class MemFo:
|
|
|
282
290
|
else: # human
|
|
283
291
|
self.divisor = 0 # human
|
|
284
292
|
self.precision = 0
|
|
285
|
-
self.data_width = 1
|
|
293
|
+
self.data_width, self.slice_width = 1, 1
|
|
286
294
|
self.data_width = len(self.render(-self.max_value))
|
|
295
|
+
if self.opts.clock == 'mono':
|
|
296
|
+
self.slice_width = max(self.data_width, len('300d23h'))
|
|
297
|
+
else:
|
|
298
|
+
self.slice_width = max(self.data_width, len('Mon 11/30'))
|
|
299
|
+
|
|
287
300
|
|
|
288
301
|
def render(self, value, sign=''):
|
|
289
302
|
""" Render a value into a string per the current options
|
|
@@ -294,13 +307,13 @@ class MemFo:
|
|
|
294
307
|
string = human(value)
|
|
295
308
|
if sign and string[0] != '-':
|
|
296
309
|
string = f'+{string}'
|
|
297
|
-
rv = f'{human(value):>{self.
|
|
310
|
+
rv = f'{human(value):>{self.slice_width}}'
|
|
298
311
|
else:
|
|
299
312
|
value = round(value/self.divisor, self.precision)
|
|
300
313
|
if self.precision:
|
|
301
|
-
rv = f'{value:{sign}{self.
|
|
314
|
+
rv = f'{value:{sign}{self.slice_width},.{self.precision}f}'
|
|
302
315
|
else:
|
|
303
|
-
rv = f'{int(value):{sign}{self.
|
|
316
|
+
rv = f'{int(value):{sign}{self.slice_width},d}'
|
|
304
317
|
return rv
|
|
305
318
|
|
|
306
319
|
def render_slices(self, slices):
|
|
@@ -322,20 +335,35 @@ class MemFo:
|
|
|
322
335
|
elif self.page == 'normal':
|
|
323
336
|
## mono = time.monotonic() - self.mono_start
|
|
324
337
|
text = (f'{self.dbinfo} [u]nits:{self.opts.units} [i]tvl={self.opts.report_interval}'
|
|
325
|
-
+ f' [d]eltas:{delta} zeros={zeros} Dump [e]dit ?=help [q]uit')
|
|
338
|
+
+ f' [d]eltas:{delta} zeros={zeros} Dump [c]lock [e]dit ?=help [q]uit')
|
|
326
339
|
else:
|
|
327
340
|
text = ('EDIT SCREEN: e,ENTER:return'
|
|
328
341
|
+ ' *:put-on-top -:hide-line [r]eset-line [R]reset-all-lines ?=help')
|
|
342
|
+
|
|
343
|
+
width = self.slice_width
|
|
344
|
+
self.dead_width = (len(slices)-1)*(width+1) - 1
|
|
345
|
+
|
|
329
346
|
add_row(key='_lead', text=text)
|
|
330
347
|
|
|
331
348
|
for ii, info in enumerate(slices):
|
|
332
349
|
for key in list(info.keys())[:count]:
|
|
350
|
+
text, text0, text1, text2 = None, None, None, None
|
|
333
351
|
if key == '_mono':
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
352
|
+
clock = self.opts.clock
|
|
353
|
+
dt_object = datetime.fromtimestamp(info['_time'])
|
|
354
|
+
time_str = dt_object.strftime("%a %m/%d %H:%M:%S")
|
|
355
|
+
if clock in ('mono', 'both'):
|
|
356
|
+
ago = f'{ago_str(info["_mono"])}'
|
|
357
|
+
text0 = f'{ago:>{width}}'
|
|
358
|
+
if clock in ('mono', ):
|
|
359
|
+
if ii == len(slices)-1:
|
|
360
|
+
string = time_str.split(maxsplit=1)[1]
|
|
361
|
+
text0 += f' {string}'
|
|
362
|
+
if clock in ('wall', 'both'):
|
|
363
|
+
top, bot = time_str.rsplit(maxsplit=1)
|
|
364
|
+
text1 = f'{top:>{width}}'
|
|
365
|
+
text2 = f'{bot:>{width}}'
|
|
366
|
+
|
|
339
367
|
else:
|
|
340
368
|
val = info[key]
|
|
341
369
|
if ii < len(slices)-1 and self.opts.delta:
|
|
@@ -344,19 +372,35 @@ class MemFo:
|
|
|
344
372
|
else:
|
|
345
373
|
text = self.render(val)
|
|
346
374
|
# now add the text of the file to the text of the line
|
|
347
|
-
if
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
375
|
+
if ii == 0:
|
|
376
|
+
if key.startswith('_mono'):
|
|
377
|
+
if text0:
|
|
378
|
+
add_row('_mono0', text0)
|
|
379
|
+
if text1:
|
|
380
|
+
add_row('_mono1', text1)
|
|
381
|
+
add_row('_mono2', text2)
|
|
382
|
+
elif key.startswith('_') or key in self.non_zeros:
|
|
383
|
+
add_row(key, text)
|
|
384
|
+
else:
|
|
385
|
+
peak = max([info[key] for info in slices])
|
|
386
|
+
add_row(key, text, zero=bool(peak==0))
|
|
351
387
|
else:
|
|
352
|
-
|
|
353
|
-
|
|
388
|
+
if text0:
|
|
389
|
+
rows['_mono0'].text += ' ' + text0
|
|
390
|
+
if text1:
|
|
391
|
+
rows['_mono1'].text += ' ' + text1
|
|
392
|
+
rows['_mono2'].text += ' ' + text2
|
|
393
|
+
if text:
|
|
394
|
+
if key in rows:
|
|
395
|
+
rows[key].text += ' ' + text
|
|
396
|
+
|
|
354
397
|
self.report_rows = rows
|
|
355
398
|
|
|
356
399
|
def _read_info(self):
|
|
357
400
|
self.fh.seek(0)
|
|
401
|
+
fraction, _ = math.modf(self.mono_start)
|
|
358
402
|
info = {'_mono': int(round(time.monotonic()-self.mono_start)),
|
|
359
|
-
'_time': time.time()}
|
|
403
|
+
'_time': int(round(time.time()-fraction))}
|
|
360
404
|
for line in self.fh:
|
|
361
405
|
mat = re.match(r'^([^:]+):\s*(\d+)\s*(|kB)$', line)
|
|
362
406
|
if mat:
|
|
@@ -398,7 +442,7 @@ class MemFo:
|
|
|
398
442
|
cols_width -= 4
|
|
399
443
|
|
|
400
444
|
# Maximum number of columns that can physically fit on the screen
|
|
401
|
-
max_col_cnt = max(1, cols_width // (1 + self.
|
|
445
|
+
max_col_cnt = max(1, cols_width // (1 + self.slice_width))
|
|
402
446
|
|
|
403
447
|
# 3. DETERMINE Interval Mode
|
|
404
448
|
|
|
@@ -462,21 +506,13 @@ class MemFo:
|
|
|
462
506
|
if row.key.startswith('_time'):
|
|
463
507
|
pass
|
|
464
508
|
elif row.key.startswith('_mono'):
|
|
465
|
-
live_idx = None
|
|
466
509
|
attr = curses.A_BOLD
|
|
467
510
|
if self.slicer.tack:
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
pattern = r'(\s+\S+\s+\S+\s+\S+\s*)$'
|
|
472
|
-
match = re.search(pattern, row.text)
|
|
473
|
-
if match:
|
|
474
|
-
live_idx = match.start(1)
|
|
475
|
-
if live_idx is None:
|
|
476
|
-
self.win.add_header(row.text, attr=attr)
|
|
511
|
+
wid = self.dead_width
|
|
512
|
+
self.win.add_header(row.text[:wid], attr=attr|curses.A_REVERSE)
|
|
513
|
+
self.win.add_header(row.text[wid:], attr=attr, resume=True)
|
|
477
514
|
else:
|
|
478
|
-
self.win.add_header(row.text
|
|
479
|
-
self.win.add_header(row.text[live_idx:], attr=attr, resume=True)
|
|
515
|
+
self.win.add_header(row.text, attr=attr)
|
|
480
516
|
|
|
481
517
|
elif row.key.startswith('_'):
|
|
482
518
|
self.win.add_header(row.text, attr=curses.A_BOLD)
|
|
@@ -529,7 +565,9 @@ class MemFo:
|
|
|
529
565
|
if key in (ord('i'), ):
|
|
530
566
|
self.legalize_report_interval()
|
|
531
567
|
elif key in (ord('u'), ):
|
|
532
|
-
self.
|
|
568
|
+
self._set_widths()
|
|
569
|
+
elif key in (ord('c'), ):
|
|
570
|
+
self._set_widths()
|
|
533
571
|
elif key in (ord('?'), ):
|
|
534
572
|
set_page()
|
|
535
573
|
elif key in (ord('e'), ):
|
|
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memfo"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.2"
|
|
8
8
|
description = "Display Tool for /proc/meminfo"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Joe Defen", email = "joedef@duck.com" }
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Operating System :: POSIX :: Linux"
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
|
-
'console-window
|
|
22
|
+
'console-window == 3.1.2',
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
[project.scripts]
|
memfo-2.0.2/runner
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# runner.py
|
|
3
|
+
# Located in the top-level APP directory
|
|
4
|
+
# Wrapper to run APP from source for debugging
|
|
5
|
+
|
|
6
|
+
import runpy
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# Ensure the package's top directory is on the path so 'APP' can be found.
|
|
10
|
+
# This is usually necessary for debuggers to work correctly.
|
|
11
|
+
# sys.path.insert(0, '.')
|
|
12
|
+
|
|
13
|
+
# Execute the main module of your package.
|
|
14
|
+
# This mimics the behavior of the 'python -m APP.main' command.
|
|
15
|
+
# This assumes your main execution logic (main() function) is in APP/main.py
|
|
16
|
+
if __name__ == '__main__':
|
|
17
|
+
# You pass the full module path: 'package_name.module_name'
|
|
18
|
+
sys.argv[0] = 'memfo' # This makes sys.argv look like the real command execution
|
|
19
|
+
runpy.run_module('memfo.main', run_name='__main__', alter_sys=True)
|
memfo-2.0.2/runner.py
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|