pmemstat 3.0.2__tar.gz → 3.0.3__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pmemstat
3
- Version: 3.0.2
3
+ Version: 3.0.3
4
4
  Summary: Report Proportional Memory on Linux
5
5
  Keywords: proportional memory,RAM,htop,top,monitor
6
6
  Author-email: Joe Defen <joedef@duck.com>
@@ -246,7 +246,7 @@ class ProcMem:
246
246
  # units = '' # determined by arguments
247
247
  # fwidth = 11
248
248
  pmemstat = None # the main program object
249
- max_cmd_len = 32 # command line maximum length
249
+ max_cmd_len = 96 # command line maximum length
250
250
  chunk_dict = {
251
251
  'cat': None,
252
252
  'beg': 0,
@@ -286,58 +286,137 @@ class ProcMem:
286
286
  self.cpu = CpuSmooth(self.pid, avg_secs= ProcMem.opts.cpu_avg_secs)
287
287
  return self.cpu.refresh_cpu() # sets self.cpu.percent
288
288
 
289
+ def _get_exebasename(self, exepath, wds):
290
+ """
291
+ Final, definitive, robust executable base name resolver.
292
+ """
293
+
294
+ # --- Phase 1: Determine the Initial Best Name Candidate ---
295
+ # ... (same initial logic as before, using os.readlink)
296
+ basename = os.path.basename(exepath)
297
+ is_bad_name = not basename or basename == 'exe' or len(basename) > 16 or not re.search(r'[a-zA-Z]', basename)
298
+
299
+ if is_bad_name:
300
+ try:
301
+ link_target = os.readlink(f'/proc/{self.pid}/exe')
302
+ if ' (deleted)' not in link_target and 'a.out' not in link_target:
303
+ basename = os.path.basename(link_target)
304
+ except (FileNotFoundError, OSError):
305
+ pass
306
+
307
+ # --- Phase 2: Multi-Step Aggressive Cleanup (The FINAL Final Fix) ---
308
+
309
+ # 1. Remove VQ... style hashes and generic suffixes
310
+ hash_and_bin_pattern = r'(VQ[A-Z0-9]{10,}|-bin|\.bin)'
311
+ basename = re.sub(hash_and_bin_pattern, '', basename).strip()
312
+
313
+ # 2. **CRITICAL FIX:** Remove all tab/PID/browser-related arguments from the tail.
314
+ # This addresses both 'browser 14 tab' AND any simple trailing numbers (like 'browser 4')
315
+ tab_and_num_pattern = r'(?:\s+\d+)?\s+(tab.*|socket.*|rdd.*|process.*|\d+)$'
316
+ basename = re.sub(tab_and_num_pattern, '', basename, flags=re.IGNORECASE).strip()
317
+
318
+ # 3. Remove common internal browser-role suffixes (renderer, rdd, content, etc.)
319
+ roles_pattern = r'-(renderer|gpu|utility|plugin|content|chrm|bsp|extension)$'
320
+ basename = re.sub(roles_pattern, '', basename, flags=re.IGNORECASE).strip()
321
+
322
+ # Final safety cleanup for any leading/trailing punctuation/spaces leftover
323
+ basename = re.sub(r'^\W+|\W+$', '', basename).strip()
324
+
325
+ # --- Phase 3: Last Resort Fallback ---
326
+
327
+ if not basename or basename in ('exe', 'a.out'):
328
+ try:
329
+ basename = os.path.basename(wds[0])
330
+ except IndexError:
331
+ basename = 'exe'
332
+
333
+ # Special Case Protection: Restore I3 if it was reduced
334
+ if basename == 'i':
335
+ basename = 'i3'
336
+
337
+ return basename
338
+
289
339
  def get_cmdline(self):
290
340
  """Get the command line of the PID."""
291
- try:
292
341
 
342
+ try:
293
343
  cmdline_file = f'/proc/{self.pid}/cmdline'
294
344
  try:
295
345
  # pylint: disable=consider-using-with
296
346
  line = open(cmdline_file, encoding='utf-8').read()[:-1]
297
347
  except FileNotFoundError as exc:
298
- # this seems to be a race which ignore; either the process is just
299
- # started or just quickly ended before even identified
300
348
  if DebugLevel:
301
349
  DB(1, f'skip pid={self.pid} no-rollup-lines exc={type(exc).__name__}')
302
350
  return
351
+
303
352
  arguments = line.split('\0')
304
353
  if not arguments or not arguments[0]: # kernel process
305
354
  self.wanted, self.kernel = False, True
306
- # print(f'{self.pid}: kernel thread')
307
355
  return
308
- # DB(0, f'{self.pid}: {arguments}')
309
- # sometimes the first word
310
- wds = os.path.basename(arguments[0]).split() + arguments[1:]
311
- basename = re.sub(r'^\W+', '', wds.pop(0))
312
- basename = re.sub(r'\W+$', '', basename)
313
- # DB(0, f'basename={basename} wds={wds}')
314
- if basename in ('python', 'python2', 'python3', 'perl', 'bash', 'ruby',
356
+
357
+ exepath = arguments[0]
358
+
359
+ # Prepare wds (words/arguments) for use in the helper and cmdline rebuild
360
+ base_name_list = os.path.basename(exepath).split()
361
+ if base_name_list:
362
+ initial_basename = base_name_list.pop(0)
363
+ wds = base_name_list + arguments[1:]
364
+ else:
365
+ initial_basename = ''
366
+ wds = arguments[1:]
367
+
368
+ # Use the robust helper to determine the final, clean basename
369
+ self.exebasename = self._get_exebasename(exepath, wds)
370
+
371
+ # --- START ELABORATION LOGIC ---
372
+
373
+ # 1. Elaboration: The Sudo Wrapper
374
+ if self.exebasename == 'sudo' and wds:
375
+ actual_cmd_name = os.path.basename(wds[0])
376
+ if actual_cmd_name:
377
+ self.exebasename = actual_cmd_name
378
+ del wds[0]
379
+
380
+ # 2. Elaboration: The Interpreter (Python, Perl, etc.)
381
+ if self.exebasename in ('python', 'python2', 'python3', 'perl', 'bash', 'ruby',
315
382
  'sh', 'ksh', 'zsh') and wds:
316
- script = os.path.basename(wds[0])
317
- # DB(0, f'script={script} wds[0]={wds[0]}')
318
- if script != wds[0]:
319
- basename = f'{basename}->{script}'
383
+
384
+ # 2.a. Catch the 'python -m <module>' pattern (The New Fix)
385
+ if len(wds) >= 2 and wds[0] == '-m':
386
+ module_name = wds[1]
387
+ self.exebasename = f'{self.exebasename}->{module_name}'
388
+ # Remove '-m' and the module name from arguments
320
389
  del wds[0]
321
- self.exebasename = basename
322
- self.cmdline = ' '.join([basename] + wds)
390
+ del wds[0]
391
+
392
+ # 2.b. Catch the standard '<script.py>' pattern (Original Logic)
393
+ elif wds[0] and os.path.exists(wds[0]) and os.path.isfile(wds[0]):
394
+ script = os.path.basename(wds[0])
395
+ if script != wds[0]:
396
+ # Only elaborate if it's a clear script name, not a generic main
397
+ if not re.search(r'^__main__\.', script):
398
+ self.exebasename = f'{self.exebasename}->{script}'
399
+ del wds[0] # Remove the script path from arguments
400
+
401
+ # --- END ELABORATION LOGIC ---
402
+
403
+ self.cmdline = ' '.join([self.exebasename] + wds)
323
404
  self.cmdline_trunc = self.cmdline[0:ProcMem.max_cmd_len]
324
- # DB(0, f'basename={basename} cmdline_trunc={self.cmdline}')
405
+
325
406
  except Exception as exc:
326
- # not really expecting this ... probably a bug
407
+ # ... (rest of exception handling is the same)
327
408
  print(f' WARNING: skip pid={self.pid} no-basename exc={exc}')
328
409
  print(traceback.format_exc())
329
410
  self.wanted = self.kernel = False
330
411
  self.why_not = 'CannotGetCmdline'
331
412
  return
332
413
 
333
- ## print(f'DBDB: {self.pid} {ProcMem.opts.pids}')
334
414
  # filter unwanted before too much work
335
415
  if (ProcMem.opts.pids and str(self.pid) not in ProcMem.opts.pids
336
416
  and self.exebasename not in ProcMem.opts.pids):
337
417
  self.wanted = False
338
418
  self.why_not = 'FilteredByArgs'
339
- # print(f' >>>> unwanted {pid}')
340
- ## print(f'DBDB: {self.pid} wanted={self.wanted} why_not={self.why_not}')
419
+
341
420
  self.set_key()
342
421
 
343
422
  def set_key(self):
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "pmemstat"
7
- version = "3.0.2"
7
+ version = "3.0.3"
8
8
  description = "Report Proportional Memory on Linux"
9
9
  authors = [
10
10
  {name = "Joe Defen", email = "joedef@duck.com"},
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes