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.
@@ -1,4 +1,5 @@
1
1
  # Byte-compiled / optimized / DLL files
2
+ .venv/
2
3
  __pycache__/*
3
4
  */__pycache__/*
4
5
  .vscode/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: memfo
3
- Version: 2.0.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
- Requires-Dist: console-window >= 1.0.0
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
- A highly efficient, real-time Linux memory monitoring tool built in Python.
24
+ An efficient, real-time Linux memory monitoring tool built in Python.
24
25
 
25
- memfo provides a low-overhead, stable, and highly configurable view of your system's /proc/meminfo data, designed specifically for long-term monitoring and performance debugging.
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 meminfo:
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
- A highly efficient, real-time Linux memory monitoring tool built in Python.
8
+ An efficient, real-time Linux memory monitoring tool built in Python.
9
9
 
10
- memfo provides a low-overhead, stable, and highly configurable view of your system's /proc/meminfo data, designed specifically for long-term monitoring and performance debugging.
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 meminfo:
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
- - add dump-to-csv key and it put current samples into a file.... maybe takes
7
- over header line for 10s to show feedback
8
- - ensure collection is being done during help screen and edit screen (not sure)
9
- - update README considerably
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._set_units()
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 _set_units(self):
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.data_width}}'
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.data_width},.{self.precision}f}'
314
+ rv = f'{value:{sign}{self.slice_width},.{self.precision}f}'
302
315
  else:
303
- rv = f'{int(value):{sign}{self.data_width},d}'
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
- ago = f'{ago_str(info["_mono"])}'
335
- text = f'{ago:>{self.data_width}}'
336
- if ii == len(slices)-1:
337
- time_str = datetime.now().strftime("%m/%d %H:%M:%S")
338
- text += f' {time_str}'
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 key in rows:
348
- rows[key].text += ' ' + text
349
- elif key.startswith('_') or key in self.non_zeros:
350
- add_row(key, text)
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
- peak = max([info[key] for info in slices])
353
- add_row(key, text, zero=bool(peak==0))
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.data_width))
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
- # .... 1m50s 1m55s 1m59s 10/24 16:32:10
469
- # ^
470
- # looking for start of "live" col (the ^ position)
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[:live_idx], attr=attr|curses.A_REVERSE)
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._set_units()
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.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 >= 1.0.0',
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
@@ -0,0 +1,9 @@
1
+ # runner.py (at project root)
2
+ # - use for debugging ... otherwise, it is very messy with flit
3
+
4
+ import sys
5
+ from memfo.main import run
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(run())
9
+
File without changes
File without changes
File without changes
File without changes
File without changes