smem2.smem2
1import os 2import pwd 3import re 4import sys 5from dataclasses import dataclass, field 6from typing import Set 7 8 9@dataclass 10class SmemConfig: 11 """Stores the configuration for smem2. 12 13 This dataclass holds all the configuration options for smem2, 14 mostly populated from command-line arguments. 15 """ 16 17 # from parse_arguments 18 no_header: bool = False 19 columns: str = "" 20 autosize: bool = False 21 realmem: str | None = None 22 kernel: str | None = None 23 basename: bool = False 24 quiet: bool = False 25 processfilter: str | None = None 26 mapfilter: str | None = None 27 userfilter: str | None = None 28 pid: int | None = None 29 ignorecase: bool = False 30 mappings: bool = False 31 users: bool = False 32 system: bool = False 33 sysdetail: bool = False 34 groupcmd: bool = False 35 percent: bool = False 36 abbreviate: bool = False 37 totals: bool = False 38 totalsonly: bool = False 39 format: str = "raw" 40 numeric: bool = False 41 sort: str | None = None 42 reverse: bool = False 43 cmd_width: int = -1 44 name_width: int = -1 45 user_width: int = -1 46 mapping_width: int = -1 47 48 # from setdatasources and other globals 49 # these are not set by user but determined at runtime 50 rssdetail: bool = False 51 pssdetail: bool = True 52 rollup: bool = True 53 swappss: bool = False 54 ownpid: int = 0 55 ignore_autosize: Set[str] = field(default_factory=set) 56 57 58class UIDCache(object): 59 """A cache for user ID to username mappings. 60 61 This class avoids repeated lookups of UIDs by caching the results. 62 """ 63 64 def __init__(self): 65 """Initializes the UIDCache.""" 66 self._cache = {} 67 68 def __call__(self, uid): 69 """Retrieves the username for a given UID, populating the cache if necessary. 70 71 Args: 72 uid (int): The user ID to look up. 73 74 Returns: 75 str: The username associated with the UID. 76 """ 77 return self._cache.setdefault(uid, self._getpwuid(uid)) 78 79 @staticmethod 80 def _getpwuid(uid): 81 """Gets the username for a UID. 82 83 Args: 84 uid (int): The user ID. 85 86 Returns: 87 str: The username, or the UID as a string if the lookup fails. 88 """ 89 try: 90 return pwd.getpwuid(uid)[0] 91 except KeyError: 92 return str(uid) 93 94 95class Proc(object): 96 """Helper class to handle /proc/ filesystem data""" 97 98 def __init__(self): 99 """Initializes the Proc helper.""" 100 pass 101 102 @staticmethod 103 def listdir(): 104 """Lists the contents of /proc. 105 106 Returns: 107 list[str]: A list of filenames in /proc. 108 """ 109 return os.listdir("/proc") 110 111 @staticmethod 112 def read(filename): 113 """Reads a file from the /proc filesystem. 114 115 Args: 116 filename (str): The name of the file in /proc to read. 117 118 Returns: 119 str: The content of the file. 120 121 Raises: 122 RuntimeError: If the file does not exist. 123 """ 124 if not os.path.exists("/proc/" + filename): 125 raise RuntimeError( 126 f"File /proc/{filename} does not exist, are you sure you are running on a Linux system?" 127 ) 128 return open("/proc/" + filename).read() 129 130 def readlines(self, filename): 131 """Reads a file from /proc and splits it into lines. 132 133 Args: 134 filename (str): The name of the file in /proc to read. 135 136 Returns: 137 list[str]: A list of lines from the file. 138 """ 139 return self.read(filename).splitlines(True) 140 141 def version(self): 142 """Gets the Linux kernel version string. 143 144 Returns: 145 str: The first line of /proc/version. 146 """ 147 return self.readlines("version")[0] 148 149 150class MemData(Proc): 151 """Class accessing and storing /proc/meminfo data""" 152 153 def __init__(self): 154 """Initializes MemData and parses /proc/meminfo.""" 155 self._memdata = {} 156 157 regex = re.compile("(?P<name>\\S+):\\s+(?P<amount>\\d+) kB") 158 for line in self.readlines("meminfo"): 159 match = regex.match(line) 160 if match: 161 self._memdata[match.group("name").lower()] = int(match.group("amount")) 162 163 def __call__(self, entry): 164 """Retrieves a value from the stored meminfo data. 165 166 Args: 167 entry (str): The key for the meminfo data to retrieve (e.g., 'memtotal'). 168 169 Returns: 170 int: The value from /proc/meminfo in kilobytes. 171 """ 172 return self._memdata[entry] 173 174 175class ProcessData(Proc): 176 """Helper class to handle /proc/<pid> filesystem data""" 177 178 def __init__(self, config: SmemConfig): 179 """Initializes ProcessData. 180 181 Args: 182 config (SmemConfig): The smem2 configuration. 183 """ 184 self._uidcache = UIDCache() 185 self.config = config 186 187 def _iskernel(self, pid): 188 """Checks if a process ID belongs to a kernel thread. 189 190 Kernel threads have an empty command line. 191 192 Args: 193 pid (str): The process ID. 194 195 Returns: 196 bool: True if the PID is a kernel thread, False otherwise. 197 """ 198 return self.pidcmd(pid) == "" 199 200 @staticmethod 201 def _stat(pid): 202 """Gets the stat result for a process. 203 204 Args: 205 pid (str): The process ID. 206 207 Returns: 208 os.stat_result: The result of os.stat() on /proc/<pid>. 209 """ 210 return os.stat("/proc/" + pid) 211 212 def pids(self): 213 """Gets a list of process IDs. 214 215 Filters the list based on the configuration (e.g., specific PID). 216 Excludes kernel threads and the smem2 process itself if a filter is active. 217 218 Returns: 219 list[int]: A list of process IDs. 220 """ 221 return [ 222 int(e) 223 for e in self.listdir() 224 if e.isdigit() 225 and not self._iskernel(e) 226 and ((self.config.pid and self.config.pid == int(e)) or not self.config.pid) 227 and not ((int(e) == self.config.ownpid) and self.config.processfilter) 228 ] 229 # exclude own process when processfilter 230 231 def mapdata(self, pid): 232 """Gets the smaps data for a process. 233 234 It prefers using smaps_rollup for performance if available and configured. 235 236 Args: 237 pid (str): The process ID. 238 239 Returns: 240 list[str]: The lines from the smaps or smaps_rollup file, or an empty list on error. 241 """ 242 if self.config.rollup: 243 try: 244 return self.readlines( 245 "%s/smaps_rollup" % pid 246 ) # smaps_rollup new from kernel 4.14, at least 7x faster for non maping listing 247 except Exception: 248 pass 249 try: 250 return self.readlines("%s/smaps" % pid) 251 except (RuntimeError, PermissionError): 252 return [] 253 254 def pidcmd(self, pid): 255 """Gets the command line for a process. 256 257 Args: 258 pid (str): The process ID. 259 260 Returns: 261 str: The command line of the process, or '?' on error. 262 """ 263 try: 264 c = self.read("%s/cmdline" % pid)[:-1] 265 c = c.replace("\0", " ") 266 if self.config.basename and not c == "": 267 c = os.path.basename(c.split()[0]) 268 return c 269 except Exception: 270 return "?" 271 272 def piduser(self, pid): 273 """Gets the UID of the process owner. 274 275 Args: 276 pid (str): The process ID. 277 278 Returns: 279 int: The user ID of the process owner, or -1 on error. 280 """ 281 try: 282 return self._stat("%d" % pid).st_uid 283 except Exception: 284 return -1 285 286 def username(self, uid): 287 """Gets the username for a UID. 288 289 Args: 290 uid (int): The user ID. 291 292 Returns: 293 str: The username, or '?' if the UID is -1. 294 """ 295 return "?" if uid == -1 else self._uidcache(uid) 296 297 def pidusername(self, pid): 298 """Gets the username of the process owner. 299 300 Args: 301 pid (int or str): The process ID. 302 303 Returns: 304 str: The username of the process owner. 305 """ 306 return self.username(self.piduser(pid)) 307 308 def pidtostr(self, pid): 309 """Converts a PID to a string. 310 311 Args: 312 pid (int): The process ID. 313 314 Returns: 315 str: The process ID as a string. 316 """ 317 return str(pid) 318 319 def pidname(self, pid): 320 """Gets the process name (from /proc/<pid>/comm). 321 322 Args: 323 pid (str): The process ID. 324 325 Returns: 326 str: The name of the process, or '?' on error. 327 """ 328 try: 329 return self.read("%s/comm" % pid)[:-1] 330 except Exception: 331 return "?" 332 333 334def totalmem(config: SmemConfig): 335 """Calculates the total memory to be used as a reference for percentages. 336 337 It can be based on real memory from dmidecode, a user-provided value, 338 or the 'MemTotal' value from /proc/meminfo. 339 340 Args: 341 config (SmemConfig): The smem2 configuration. 342 343 Returns: 344 int: The total memory in kilobytes. 345 """ 346 if config.realmem: 347 return fromunits(config.realmem) / 1024 348 else: 349 if config.system or config.sysdetail: 350 try: 351 # only for systems running directly on HW 352 ram = ( 353 os.popen( 354 "dmidecode --type memory 2> /dev/null|awk '/Size: [0-9]+ MB/{sum+=$2};END{print sum}'", 355 ) 356 .read() 357 .strip() 358 ) 359 if ram.isdigit(): 360 return fromunits(str(int(ram)) + "M") / 1024 361 except Exception: 362 pass 363 364 return MemData()("memtotal") 365 366 367def pidmaps(pid, proc: ProcessData, config: SmemConfig, nomaps=False): 368 """Parses the memory maps for a given process. 369 370 It reads /proc/<pid>/smaps or /proc/<pid>/smaps_rollup to get memory 371 usage information for each mapping. 372 373 Args: 374 pid (int or str): The process ID. 375 proc (ProcessData): The ProcessData instance. 376 config (SmemConfig): The smem2 configuration. 377 nomaps (bool, optional): If True, only read the first mapping entry, 378 used for feature detection. Defaults to False. 379 380 Returns: 381 dict: A dictionary of memory mappings, where keys are start addresses. 382 """ 383 maps: dict = {} 384 start = None 385 status = None 386 if config.rollup or config.pssdetail or config.rssdetail: 387 try: 388 status = proc.readlines("%s/status" % pid) 389 except (RuntimeError, PermissionError): 390 status = None 391 392 for l in proc.mapdata(pid): 393 f = l.split() 394 if f[-1] == "kB": 395 maps[start][f[0][:-1].lower()] = int(f[1]) 396 elif "-" in f[0] and ":" not in f[0]: # looks like a mapping range 397 start, end = f[0].split("-") 398 start = int(start, 16) 399 name = "<anonymous>" 400 if len(f) > 5: 401 name = f[5] 402 if config.basename: 403 name = os.path.basename(name) 404 maps[start] = dict( 405 end=int(end, 16), 406 perm=f[1], 407 offset=int(f[2], 16), 408 major=int(f[3].split(":")[0], 16), 409 minor=int(f[3].split(":")[1], 16), 410 inode=int(f[4]), 411 name=name, 412 pss=0, 413 rss=0, 414 uss=0, 415 swap=0, 416 swappss=0, 417 pcent=0, 418 size=0, 419 ) 420 if config.pssdetail: 421 ( 422 maps[start]["pss_anon"], 423 maps[start]["pss_file"], 424 maps[start]["pss_shmem"], 425 ) = (0, 0, 0) 426 427 # for nomaps mode: stop reading mappings 428 if nomaps: 429 break 430 431 # add pss, rss, uss if available from smaps 432 if not config.rollup: 433 if not config.pssdetail: 434 for m in maps: 435 maps[m]["uss"] = maps[m].get("private_clean", 0) + maps[m].get( 436 "private_dirty", 0 437 ) 438 maps[m]["pss"] = maps[m]["uss"] + maps[m].get("shared_clean", 0) / 2 439 maps[m]["rss"] = maps[m].get("shared_clean", 0) + maps[m].get( 440 "shared_dirty", 0 441 ) 442 if config.rssdetail and status: 443 # from /proc/pid/status 444 for s in status: 445 if "RssAnon" in s: 446 maps[start]["rss_anon"] = int(s.split()[1]) 447 elif "RssFile" in s: 448 maps[start]["rss_file"] = int(s.split()[1]) 449 elif "RssShmem" in s: 450 maps[start]["rss_shmem"] = int(s.split()[1]) 451 452 # Parse VmSize for VSS calculation 453 if status: 454 for s in status: 455 if "VmSize:" in s: 456 # Set size for the last mapping (rollup case) or all mappings 457 if config.rollup and start: 458 maps[start]["size"] = int(s.split()[1]) 459 else: 460 # For non-rollup, distribute VmSize across all mappings 461 total_size = int(s.split()[1]) 462 if maps: 463 size_per_map = ( 464 total_size // len(maps) if len(maps) > 0 else 0 465 ) 466 for m in maps: 467 maps[m]["size"] = size_per_map 468 break 469 470 # Apply mapfilter if specified 471 if config.mapfilter and not nomaps: 472 f = {} 473 for m in maps: 474 if not filters(config.mapfilter, m, config, lambda x: maps[x]["name"]): 475 f[m] = maps[m] 476 return f 477 return maps 478 479 480def maptotals(pids, proc: ProcessData, config: SmemConfig): 481 """Aggregates memory usage data for a list of pids, including map details. 482 483 For each process, it gets memory totals (PSS, RSS, USS, etc.) and 484 detailed mapping information. It applies process and user filters. 485 486 Args: 487 pids (list[int]): A list of process IDs. 488 proc (ProcessData): The ProcessData instance. 489 config (SmemConfig): The smem2 configuration. 490 491 Returns: 492 dict: A dictionary where keys are process IDs and values are dictionaries 493 containing memory totals and mapping details for each process. 494 """ 495 pt: dict = {} 496 for p in pids: 497 if filters( 498 config.processfilter, p, config, proc.pidcmd, proc.pidtostr, proc.pidname 499 ) or filters(config.userfilter, p, config, proc.pidusername): 500 continue 501 maps = pidmaps(p, proc, config) 502 if maps: 503 pt[p] = {} 504 pt[p]["maps"] = maps 505 totals = pidtotals(p, proc, config) 506 # Flatten the totals into the main dict for compatibility with display code 507 pt[p].update(totals) 508 return pt 509 510 511def pidtotals(pid, proc: ProcessData, config: SmemConfig): 512 """Calculates the total memory usage (PSS, RSS, USS, etc.) for a single process. 513 514 It sums up the values from all memory mappings of the process. 515 516 Args: 517 pid (int or str): The process ID. 518 proc (ProcessData): The ProcessData instance. 519 config (SmemConfig): The smem2 configuration. 520 521 Returns: 522 dict: A dictionary with total PSS, RSS, USS, Swap, etc., for the process. 523 """ 524 totals: dict = {} 525 maps = pidmaps(pid, proc, config) 526 if maps: 527 maplist = maps.values() 528 totals["pss"] = sum([m.get("pss", 0) for m in maplist]) 529 totals["rss"] = sum([m.get("rss", 0) for m in maplist]) 530 private_clean = sum(m.get("private_clean", 0) for m in maplist) 531 private_dirty = sum(m.get("private_dirty", 0) for m in maplist) 532 totals["uss"] = private_clean + private_dirty 533 totals["swap"] = sum([m.get("swap", 0) for m in maplist]) 534 totals["size"] = sum([m.get("size", 0) for m in maplist]) 535 totals["maps"] = len(maps) 536 if config.swappss: 537 totals["swappss"] = sum([m.get("swappss", 0) for m in maplist]) 538 if config.pssdetail: 539 totals["pss_anon"] = sum([m.get("pss_anon", 0) for m in maplist]) 540 totals["pss_file"] = sum([m.get("pss_file", 0) for m in maplist]) 541 totals["pss_shmem"] = sum([m.get("pss_shmem", 0) for m in maplist]) 542 if config.rssdetail: 543 totals["rss_anon"] = sum([m.get("rss_anon", 0) for m in maplist]) 544 totals["rss_file"] = sum([m.get("rss_file", 0) for m in maplist]) 545 totals["rss_shmem"] = sum([m.get("rss_shmem", 0) for m in maplist]) 546 return totals 547 548 549def usertotals(pids, proc: ProcessData, config: SmemConfig): 550 """Aggregates memory usage by user. 551 552 It groups processes by user and calculates the total memory usage for each user. 553 554 Args: 555 pids (list[int]): A list of process IDs to analyze. 556 proc (ProcessData): The ProcessData instance. 557 config (SmemConfig): The smem2 configuration. 558 559 Returns: 560 dict: A dictionary where keys are user IDs and values are dictionaries 561 containing process lists and memory totals for each user. 562 """ 563 ut: dict = {} 564 for p in pids: 565 if filters( 566 config.processfilter, p, config, proc.pidcmd, proc.pidtostr, proc.pidname 567 ) or filters(config.userfilter, p, config, proc.pidusername): 568 continue 569 u = proc.piduser(p) 570 if u not in ut: 571 ut[u] = {"pids": [], "name": "?"} 572 ut[u]["name"] = proc.username(u) 573 ut[u]["pids"].append(p) 574 575 # get totals per user 576 for u in ut: 577 ut[u]["totals"] = processtotals(ut[u]["pids"], proc, config) 578 return ut 579 580 581def cmdtotals(pids, proc: ProcessData, config: SmemConfig): 582 """Aggregates memory usage by command line. 583 584 It groups processes by their command line and calculates the total memory 585 usage for each command. 586 587 Args: 588 pids (list[int]): A list of process IDs to analyze. 589 proc (ProcessData): The ProcessData instance. 590 config (SmemConfig): The smem2 configuration. 591 592 Returns: 593 dict: A dictionary where keys are command lines and values are 594 dictionaries containing process lists and memory totals. 595 """ 596 # totals per command 597 ct: dict = {} 598 599 for p in pids: 600 if filters( 601 config.processfilter, p, config, proc.pidcmd, proc.pidtostr, proc.pidname 602 ) or filters(config.userfilter, p, config, proc.pidusername): 603 continue 604 cmdline = proc.pidcmd(p) 605 if not cmdline: 606 continue 607 608 parts = cmdline.split() 609 if not parts: 610 continue 611 c = parts[0] 612 613 # if multiple processes per command, then a list of pids 614 if c in ct: 615 ct[c]["pids"].append(p) 616 else: 617 # first pid for this command 618 ct[c] = {"pids": [p]} 619 620 # get totals per command 621 for c in ct: 622 ct[c]["totals"] = processtotals(ct[c]["pids"], proc, config) 623 if "totals" in ct[c] and ct[c]["totals"]: 624 ct[c]["name"] = c 625 ct[c]["user"] = proc.pidusername(ct[c]["pids"][0]) 626 627 # remove commands with empty totals 628 for c in list(ct.keys()): 629 if "totals" not in ct[c] or not ct[c]["totals"]: 630 del ct[c] 631 return ct 632 633 634def sortmaps(totals, key): 635 """Sorts a dictionary of totals by a given key. 636 637 Args: 638 totals (dict): A dictionary where values are another dictionary 639 containing a 'totals' key. 640 key (str): The key within the nested 'totals' dictionary to sort by. 641 642 Returns: 643 list: A sorted list of the input dictionary's values. 644 """ 645 # sort by key 646 return sorted(totals.values(), key=lambda x: x["totals"][key], reverse=True) 647 648 649def units(x): 650 """Converts a value in bytes to a human-readable string with units (K, M, G). 651 652 Args: 653 x (int or float): The value in bytes. 654 655 Returns: 656 str: A human-readable string representation of the value. 657 """ 658 if x > 1024 * 1024 * 1024: 659 return "%.1fG" % (x / (1024.0 * 1024 * 1024)) 660 if x > 1024 * 1024: 661 return "%.1fM" % (x / (1024.0 * 1024)) 662 if x > 1024: 663 return "%.1fK" % (x / 1024.0) 664 return str(x) 665 666 667def fromunits(x): 668 """Converts a human-readable string with units (K, M, G, etc.) to bytes. 669 670 Args: 671 x (str): The string with a value and unit (e.g., "1024M"). 672 673 Returns: 674 int: The value in bytes. 675 676 Raises: 677 SystemExit: If the unit is not recognized. 678 """ 679 s = dict( 680 k=2**10, 681 K=2**10, 682 kB=2**10, 683 KB=2**10, 684 M=2**20, 685 MB=2**20, 686 G=2**30, 687 GB=2**30, 688 T=2**40, 689 TB=2**40, 690 ) 691 for k, v in list(s.items()): 692 if x.endswith(k): 693 return int(float(x[: -len(k)]) * v) 694 sys.stderr.write("Memory size should be written with units, for example 1024M\n") 695 sys.exit(-1) 696 697 698def processtotals(pids, proc: ProcessData, config: SmemConfig): 699 """Sums up memory usage totals for a list of processes. 700 701 Args: 702 pids (list[int]): The list of process IDs. 703 proc (ProcessData): The ProcessData instance. 704 config (SmemConfig): The smem2 configuration. 705 706 Returns: 707 dict: A dictionary with the summed PSS, RSS, USS, etc., for the 708 given processes. 709 """ 710 # Sums up totals of a pid list 711 t = {"uss": 0, "pss": 0, "rss": 0, "swap": 0, "size": 0} 712 if config.swappss: 713 t["swappss"] = 0 714 for p in pids: 715 pt = pidtotals(p, proc, config) 716 if pt: 717 for k in t: 718 if k in pt: 719 t[k] += pt[k] 720 return t 721 722 723def get_process_data(proc: ProcessData, config: SmemConfig): 724 """Collects and returns memory usage data for each process. 725 726 Args: 727 proc (ProcessData): The ProcessData instance. 728 config (SmemConfig): The smem2 configuration. 729 730 Returns: 731 dict: A dictionary containing memory data for each process, 732 as returned by maptotals. 733 """ 734 # Collect process data 735 pids = proc.pids() 736 data = maptotals(pids, proc, config) 737 return data 738 739 740def get_map_data(proc: ProcessData, config: SmemConfig): 741 """Collects and returns memory usage data aggregated by mapping name. 742 743 Args: 744 proc (ProcessData): The ProcessData instance. 745 config (SmemConfig): The smem2 configuration. 746 747 Returns: 748 dict: A dictionary containing memory data aggregated by mapping name. 749 """ 750 # Collect map data - aggregate by mapping name 751 pids = proc.pids() 752 data = mapnametotals(pids, proc, config) 753 return data 754 755 756def mapnametotals(pids, proc: ProcessData, config: SmemConfig): 757 """Aggregates memory usage by mapping name across a list of processes. 758 759 It iterates through processes and their memory maps, summing up memory 760 statistics for each unique mapping name. 761 762 Args: 763 pids (list[int]): A list of process IDs. 764 proc (ProcessData): The ProcessData instance. 765 config (SmemConfig): The smem2 configuration. 766 767 Returns: 768 dict: A dictionary where keys are mapping names and values are 769 dictionaries of aggregated memory statistics. 770 """ 771 totals: dict = {} 772 for pid in pids: 773 # Apply filters like the original version 774 if filters( 775 config.processfilter, pid, config, proc.pidcmd, proc.pidtostr, proc.pidname 776 ) or filters(config.userfilter, pid, config, proc.pidusername): 777 continue 778 try: 779 maps = pidmaps(pid, proc, config) 780 seen = {} 781 for m in list(maps.keys()): 782 name = maps[m]["name"] 783 if name not in totals: 784 t = dict( 785 size=0, 786 rss=0, 787 pss=0, 788 shared_clean=0, 789 shared_dirty=0, 790 private_clean=0, 791 count=0, 792 private_dirty=0, 793 referenced=0, 794 swap=0, 795 pids=0, 796 ) 797 if config.swappss: 798 t["swappss"] = 0 799 else: 800 t = totals[name] 801 for k in t: 802 t[k] += maps[m].get(k, 0) 803 t["count"] += 1 804 if name not in seen: 805 t["pids"] += 1 806 seen[name] = 1 807 totals[name] = t 808 except (OSError, IOError): 809 continue 810 return totals 811 812 813def get_user_data(proc: ProcessData, config: SmemConfig): 814 """Collects and returns memory usage data aggregated by user. 815 816 Args: 817 proc (ProcessData): The ProcessData instance. 818 config (SmemConfig): The smem2 configuration. 819 820 Returns: 821 dict: A dictionary containing memory data aggregated by user. 822 """ 823 # Collect user data 824 pids = proc.pids() 825 data = usertotals(pids, proc, config) 826 return data 827 828 829def get_cmd_data(proc: ProcessData, config: SmemConfig): 830 """Collects and returns memory usage data aggregated by command. 831 832 Args: 833 proc (ProcessData): The ProcessData instance. 834 config (SmemConfig): The smem2 configuration. 835 836 Returns: 837 dict: A dictionary containing memory data aggregated by command. 838 """ 839 # Collect cmd data 840 pids = proc.pids() 841 data = cmdtotals(pids, proc, config) 842 return data 843 844 845def get_system_data(proc: ProcessData, config: SmemConfig): 846 """Collects and returns system-wide memory usage data. 847 848 This provides a breakdown of memory usage by different system components 849 like kernel, firmware, userspace, etc. 850 851 Args: 852 proc (ProcessData): The ProcessData instance. 853 config (SmemConfig): The smem2 configuration. 854 855 Returns: 856 list[tuple]: A list of tuples, where each tuple represents a memory 857 area and its usage in PSS and RSS. 858 """ 859 t = totalmem(config) 860 m = MemData() 861 mt = m("memtotal") 862 f = m("memfree") 863 kernel = kernelsize(config) 864 865 # total amount mapped into userspace (ie mapped an unmapped pages) /dev/zero mappings 866 u = m("anonpages") + m("mapped") 867 868 # total amount used by hardware 869 fh = int(max(t - mt - kernel, 0)) 870 871 # total amount allocated by kernel not for userspace 872 kd = mt - f - u 873 874 # total amount in kernel caches 875 kdc = m("buffers") + m("sreclaimable") + m("cached") - m("mapped") 876 877 if config.sysdetail: 878 shmp = mapshared(proc, config) # shared memory mapped by processes 879 mapzero = mapdevzero( 880 proc, config 881 ) # dev/zero mapped as files should be subtracted from files Mapped from userspace 882 mapped = m("mapped") - mapzero 883 modules = kernelmodsize(proc, config) 884 shm = m("shmem") - shmp 885 886 mounts = proc.readlines("mounts") 887 ramfs = 0 888 for r in mounts: 889 if "ramfs" in r: 890 try: 891 ramfs += int( 892 (os.popen("du -sk %s" % r.split()[1]).read()).split()[0] 893 ) 894 except Exception: 895 pass 896 filecache = m("cached") - mapped - shm - ramfs 897 unknown = ( 898 mt 899 - f 900 - u 901 - ( 902 modules 903 + m("pagetables") 904 + m("kernelstack") 905 + m("slab") 906 + m("buffers") 907 + filecache 908 + shm 909 + ramfs 910 ) 911 ) 912 if unknown < 0: 913 unknown = 0 914 915 lines: list[tuple] 916 if config.sysdetail: 917 lines = [ 918 ("firmware/hardware", fh, 0, 0), 919 ("kernel image", kernel, 0, 0), 920 ("kernel modules", modules, 0, 0), 921 ("page tables", m("pagetables"), 0, 0), 922 ("kernel stack", m("kernelstack"), 0, 0), 923 ("slab (all/SReclaimable)", m("slab"), m("sreclaimable"), 0), 924 ("buffers", m("buffers"), m("buffers"), 0), 925 ("cached (w/o mapped,tmpfs,ramfs)", filecache, filecache, 0), 926 ("shared (non process tmpfs)", shm, 0, 0), 927 ("ramfs", ramfs, 0, 0), 928 ("unknown", unknown, 0, 0), 929 ("processes (all/mapped files)", u, mapped, 0), 930 ("free memory", f, f, 0), 931 ] 932 if "all" in config.columns or "details" in config.columns: 933 line_all: list[tuple] = [ 934 ("-------------------------------", 0, 0, 0), 935 ("/dev/zero mapped", 0, 0, mapzero), 936 ("shared by processes", 0, 0, shmp), 937 ("unevictable", 0, 0, m("unevictable")), 938 ("dirty (unwritten to file)", 0, 0, m("dirty")), 939 ("swapped", 0, 0, m("swaptotal") - m("swapfree")), 940 ("available (estimated)", 0, 0, m("memavailable")), 941 ] 942 lines = lines + line_all 943 else: 944 lines = [ 945 ("firmware/hardware", fh, 0), 946 ("kernel image", kernel, 0), 947 ("kernel dynamic memory", kd, kdc), 948 ("userspace memory", u, m("mapped")), 949 ("free memory", f, f), 950 ] 951 return lines 952 953 954def mapdevzero(proc: ProcessData, config: SmemConfig): 955 """Calculates memory mapped to /dev/zero. 956 957 Some Linux versions count memory mapped to /dev/zero as 'Mapped' instead 958 of 'AnonPages'. This function calculates that amount. 959 960 Args: 961 proc (ProcessData): The ProcessData instance. 962 config (SmemConfig): The smem2 configuration. 963 964 Returns: 965 int: Total PSS of memory mapped to /dev/zero, in kilobytes. 966 """ 967 ps = proc.pids() 968 config.mapfilter = "^/dev/zero$" 969 pt = maptotals(ps, proc, config) 970 t = 0.0 971 for r in pt: 972 t += pt[r]["pss"] 973 return int(t) 974 975 976def mapshared(proc: ProcessData, config: SmemConfig): 977 """Calculates memory shared by multiple processes (via SYSV IPC). 978 979 Args: 980 proc (ProcessData): The ProcessData instance. 981 config (SmemConfig): The smem2 configuration. 982 983 Returns: 984 int: Total PSS of memory from SYSV shared segments, in kilobytes. 985 """ 986 ps = proc.pids() 987 config.mapfilter = "^/SYSV*" 988 pt = maptotals(ps, proc, config) 989 t = 0.0 990 for r in pt: 991 t += pt[r]["pss"] 992 return int(t) 993 994 995def kernelsize(config: SmemConfig): 996 """Determines the size of the kernel image from the file specified. 997 998 Args: 999 config (SmemConfig): The smem2 configuration. 1000 1001 Returns: 1002 int: The kernel image size in kilobytes. 1003 """ 1004 kernelsize_val = 0 1005 if config.kernel: 1006 try: 1007 d = os.popen("size %s" % config.kernel).readlines()[1].split() 1008 if int(d[1]) == 0: # data part missing, seems like packed file 1009 # try some heuristic to find gzipped part in kernel image 1010 with open(config.kernel, "rb") as f: 1011 packedkernel = f.read() 1012 pos = packedkernel.find(b"\x1f\x8b") 1013 if pos >= 0 and pos < 25000: 1014 if not config.quiet: 1015 sys.stderr.write( 1016 "Parameter '%s' should be an original uncompressed compiled kernel file.\n" 1017 % config.kernel 1018 ) 1019 sys.stderr.write( 1020 "Maybe uncompressed kernel can be extracted by the command:\n" 1021 " dd if=%s bs=1 skip=%d | gzip -d >%s.unpacked\n\n" 1022 % (config.kernel, pos, config.kernel) 1023 ) 1024 else: 1025 kernelsize_val = int(int(d[3]) / 1024 + 0.5) 1026 except Exception: 1027 pass # Fail silently if size command fails or kernel file is not found 1028 return kernelsize_val 1029 1030 1031def kernelmodsize(proc: ProcessData, config: SmemConfig): 1032 """Calculates the total size of loaded kernel modules. 1033 1034 It reads /proc/modules to get the size of each module. 1035 1036 Args: 1037 proc (ProcessData): The ProcessData instance. 1038 config (SmemConfig): The smem2 configuration. 1039 1040 Returns: 1041 int: The total size of kernel modules in kilobytes. 1042 """ 1043 ms = 0.0 1044 try: 1045 md = proc.readlines("modules") 1046 for m in md: 1047 ms += int(m.split()[1]) 1048 ms /= 1024 1049 except Exception: 1050 if not config.quiet: 1051 sys.stderr.write("Detection of kernel modules size failed\n") 1052 return int(ms) 1053 1054 1055def setdatasources(config: SmemConfig, proc: ProcessData): 1056 """Detects kernel features and updates the configuration accordingly. 1057 1058 It checks for support of PSS, SwapPSS, and detailed PSS/RSS reporting 1059 by inspecting the smaps of the current process. It also determines if 1060 the faster smaps_rollup can be used. 1061 1062 Args: 1063 config (SmemConfig): The smem2 configuration object to update. 1064 proc (ProcessData): The ProcessData instance. 1065 1066 Returns: 1067 SmemConfig: The updated configuration object. 1068 """ 1069 """New kernel features detection""" 1070 config.ownpid = os.getpid() 1071 1072 config.rssdetail = False 1073 config.pssdetail = True 1074 config.rollup = True 1075 rd = proc.readlines("%s/status" % config.ownpid) 1076 for r in rd: 1077 if "RssAnon:" in r: 1078 config.rssdetail = True 1079 break 1080 1081 map_data = pidmaps(config.ownpid, proc, config, nomaps=True) 1082 map_data = map_data[list(map_data.keys())[0]] 1083 1084 if "pss" not in map_data and not config.quiet: 1085 sys.stderr.write("Warning: Kernel does not appear to support PSS measurement\n") 1086 1087 if "swappss" in map_data: 1088 config.swappss = True 1089 else: 1090 config.swappss = False 1091 if not config.quiet: 1092 sys.stderr.write( 1093 "Warning: Kernel does not appear to support SwapPSS measurement\n" 1094 ) 1095 1096 if "pss_anon" in map_data: 1097 config.pssdetail = True 1098 else: 1099 config.pssdetail = False 1100 if not config.quiet: 1101 sys.stderr.write( 1102 "Warning: Kernel does not appear to support Pss Anon/File/Shmem measurement\n" 1103 ) 1104 1105 """ some smem2 features need full smaps""" 1106 if ( 1107 config.mapfilter 1108 or config.mappings 1109 or "maps" in config.columns 1110 or ("all" in config.columns and not config.groupcmd) 1111 ): 1112 config.rollup = False 1113 return config 1114 1115 1116def filters(opt, arg, config: SmemConfig, *sources): 1117 """A generic filter function based on regular expressions. 1118 1119 It checks if a given argument should be filtered out by matching a regex 1120 against one or more source strings. 1121 1122 Args: 1123 opt (str or None): The regex pattern to search for. If None or empty, 1124 the function returns False. 1125 arg: The argument to pass to the source functions (e.g., a PID). 1126 config (SmemConfig): The smem2 configuration (for ignorecase flag). 1127 *sources (callable): Variable length list of functions that take `arg` 1128 and return a string to be searched. 1129 1130 Returns: 1131 bool: True if the item should be filtered out (no match found), 1132 False otherwise (a match was found or no filter is active). 1133 """ 1134 if not opt: 1135 return False 1136 for f in sources: 1137 if re.search(opt, f(arg), re.I if config.ignorecase else 0): 1138 return False 1139 return True
10@dataclass 11class SmemConfig: 12 """Stores the configuration for smem2. 13 14 This dataclass holds all the configuration options for smem2, 15 mostly populated from command-line arguments. 16 """ 17 18 # from parse_arguments 19 no_header: bool = False 20 columns: str = "" 21 autosize: bool = False 22 realmem: str | None = None 23 kernel: str | None = None 24 basename: bool = False 25 quiet: bool = False 26 processfilter: str | None = None 27 mapfilter: str | None = None 28 userfilter: str | None = None 29 pid: int | None = None 30 ignorecase: bool = False 31 mappings: bool = False 32 users: bool = False 33 system: bool = False 34 sysdetail: bool = False 35 groupcmd: bool = False 36 percent: bool = False 37 abbreviate: bool = False 38 totals: bool = False 39 totalsonly: bool = False 40 format: str = "raw" 41 numeric: bool = False 42 sort: str | None = None 43 reverse: bool = False 44 cmd_width: int = -1 45 name_width: int = -1 46 user_width: int = -1 47 mapping_width: int = -1 48 49 # from setdatasources and other globals 50 # these are not set by user but determined at runtime 51 rssdetail: bool = False 52 pssdetail: bool = True 53 rollup: bool = True 54 swappss: bool = False 55 ownpid: int = 0 56 ignore_autosize: Set[str] = field(default_factory=set)
Stores the configuration for smem2.
This dataclass holds all the configuration options for smem2, mostly populated from command-line arguments.
59class UIDCache(object): 60 """A cache for user ID to username mappings. 61 62 This class avoids repeated lookups of UIDs by caching the results. 63 """ 64 65 def __init__(self): 66 """Initializes the UIDCache.""" 67 self._cache = {} 68 69 def __call__(self, uid): 70 """Retrieves the username for a given UID, populating the cache if necessary. 71 72 Args: 73 uid (int): The user ID to look up. 74 75 Returns: 76 str: The username associated with the UID. 77 """ 78 return self._cache.setdefault(uid, self._getpwuid(uid)) 79 80 @staticmethod 81 def _getpwuid(uid): 82 """Gets the username for a UID. 83 84 Args: 85 uid (int): The user ID. 86 87 Returns: 88 str: The username, or the UID as a string if the lookup fails. 89 """ 90 try: 91 return pwd.getpwuid(uid)[0] 92 except KeyError: 93 return str(uid)
A cache for user ID to username mappings.
This class avoids repeated lookups of UIDs by caching the results.
96class Proc(object): 97 """Helper class to handle /proc/ filesystem data""" 98 99 def __init__(self): 100 """Initializes the Proc helper.""" 101 pass 102 103 @staticmethod 104 def listdir(): 105 """Lists the contents of /proc. 106 107 Returns: 108 list[str]: A list of filenames in /proc. 109 """ 110 return os.listdir("/proc") 111 112 @staticmethod 113 def read(filename): 114 """Reads a file from the /proc filesystem. 115 116 Args: 117 filename (str): The name of the file in /proc to read. 118 119 Returns: 120 str: The content of the file. 121 122 Raises: 123 RuntimeError: If the file does not exist. 124 """ 125 if not os.path.exists("/proc/" + filename): 126 raise RuntimeError( 127 f"File /proc/{filename} does not exist, are you sure you are running on a Linux system?" 128 ) 129 return open("/proc/" + filename).read() 130 131 def readlines(self, filename): 132 """Reads a file from /proc and splits it into lines. 133 134 Args: 135 filename (str): The name of the file in /proc to read. 136 137 Returns: 138 list[str]: A list of lines from the file. 139 """ 140 return self.read(filename).splitlines(True) 141 142 def version(self): 143 """Gets the Linux kernel version string. 144 145 Returns: 146 str: The first line of /proc/version. 147 """ 148 return self.readlines("version")[0]
Helper class to handle /proc/ filesystem data
103 @staticmethod 104 def listdir(): 105 """Lists the contents of /proc. 106 107 Returns: 108 list[str]: A list of filenames in /proc. 109 """ 110 return os.listdir("/proc")
Lists the contents of /proc.
Returns:
list[str]: A list of filenames in /proc.
112 @staticmethod 113 def read(filename): 114 """Reads a file from the /proc filesystem. 115 116 Args: 117 filename (str): The name of the file in /proc to read. 118 119 Returns: 120 str: The content of the file. 121 122 Raises: 123 RuntimeError: If the file does not exist. 124 """ 125 if not os.path.exists("/proc/" + filename): 126 raise RuntimeError( 127 f"File /proc/{filename} does not exist, are you sure you are running on a Linux system?" 128 ) 129 return open("/proc/" + filename).read()
Reads a file from the /proc filesystem.
Arguments:
- filename (str): The name of the file in /proc to read.
Returns:
str: The content of the file.
Raises:
- RuntimeError: If the file does not exist.
131 def readlines(self, filename): 132 """Reads a file from /proc and splits it into lines. 133 134 Args: 135 filename (str): The name of the file in /proc to read. 136 137 Returns: 138 list[str]: A list of lines from the file. 139 """ 140 return self.read(filename).splitlines(True)
Reads a file from /proc and splits it into lines.
Arguments:
- filename (str): The name of the file in /proc to read.
Returns:
list[str]: A list of lines from the file.
151class MemData(Proc): 152 """Class accessing and storing /proc/meminfo data""" 153 154 def __init__(self): 155 """Initializes MemData and parses /proc/meminfo.""" 156 self._memdata = {} 157 158 regex = re.compile("(?P<name>\\S+):\\s+(?P<amount>\\d+) kB") 159 for line in self.readlines("meminfo"): 160 match = regex.match(line) 161 if match: 162 self._memdata[match.group("name").lower()] = int(match.group("amount")) 163 164 def __call__(self, entry): 165 """Retrieves a value from the stored meminfo data. 166 167 Args: 168 entry (str): The key for the meminfo data to retrieve (e.g., 'memtotal'). 169 170 Returns: 171 int: The value from /proc/meminfo in kilobytes. 172 """ 173 return self._memdata[entry]
Class accessing and storing /proc/meminfo data
154 def __init__(self): 155 """Initializes MemData and parses /proc/meminfo.""" 156 self._memdata = {} 157 158 regex = re.compile("(?P<name>\\S+):\\s+(?P<amount>\\d+) kB") 159 for line in self.readlines("meminfo"): 160 match = regex.match(line) 161 if match: 162 self._memdata[match.group("name").lower()] = int(match.group("amount"))
Initializes MemData and parses /proc/meminfo.
176class ProcessData(Proc): 177 """Helper class to handle /proc/<pid> filesystem data""" 178 179 def __init__(self, config: SmemConfig): 180 """Initializes ProcessData. 181 182 Args: 183 config (SmemConfig): The smem2 configuration. 184 """ 185 self._uidcache = UIDCache() 186 self.config = config 187 188 def _iskernel(self, pid): 189 """Checks if a process ID belongs to a kernel thread. 190 191 Kernel threads have an empty command line. 192 193 Args: 194 pid (str): The process ID. 195 196 Returns: 197 bool: True if the PID is a kernel thread, False otherwise. 198 """ 199 return self.pidcmd(pid) == "" 200 201 @staticmethod 202 def _stat(pid): 203 """Gets the stat result for a process. 204 205 Args: 206 pid (str): The process ID. 207 208 Returns: 209 os.stat_result: The result of os.stat() on /proc/<pid>. 210 """ 211 return os.stat("/proc/" + pid) 212 213 def pids(self): 214 """Gets a list of process IDs. 215 216 Filters the list based on the configuration (e.g., specific PID). 217 Excludes kernel threads and the smem2 process itself if a filter is active. 218 219 Returns: 220 list[int]: A list of process IDs. 221 """ 222 return [ 223 int(e) 224 for e in self.listdir() 225 if e.isdigit() 226 and not self._iskernel(e) 227 and ((self.config.pid and self.config.pid == int(e)) or not self.config.pid) 228 and not ((int(e) == self.config.ownpid) and self.config.processfilter) 229 ] 230 # exclude own process when processfilter 231 232 def mapdata(self, pid): 233 """Gets the smaps data for a process. 234 235 It prefers using smaps_rollup for performance if available and configured. 236 237 Args: 238 pid (str): The process ID. 239 240 Returns: 241 list[str]: The lines from the smaps or smaps_rollup file, or an empty list on error. 242 """ 243 if self.config.rollup: 244 try: 245 return self.readlines( 246 "%s/smaps_rollup" % pid 247 ) # smaps_rollup new from kernel 4.14, at least 7x faster for non maping listing 248 except Exception: 249 pass 250 try: 251 return self.readlines("%s/smaps" % pid) 252 except (RuntimeError, PermissionError): 253 return [] 254 255 def pidcmd(self, pid): 256 """Gets the command line for a process. 257 258 Args: 259 pid (str): The process ID. 260 261 Returns: 262 str: The command line of the process, or '?' on error. 263 """ 264 try: 265 c = self.read("%s/cmdline" % pid)[:-1] 266 c = c.replace("\0", " ") 267 if self.config.basename and not c == "": 268 c = os.path.basename(c.split()[0]) 269 return c 270 except Exception: 271 return "?" 272 273 def piduser(self, pid): 274 """Gets the UID of the process owner. 275 276 Args: 277 pid (str): The process ID. 278 279 Returns: 280 int: The user ID of the process owner, or -1 on error. 281 """ 282 try: 283 return self._stat("%d" % pid).st_uid 284 except Exception: 285 return -1 286 287 def username(self, uid): 288 """Gets the username for a UID. 289 290 Args: 291 uid (int): The user ID. 292 293 Returns: 294 str: The username, or '?' if the UID is -1. 295 """ 296 return "?" if uid == -1 else self._uidcache(uid) 297 298 def pidusername(self, pid): 299 """Gets the username of the process owner. 300 301 Args: 302 pid (int or str): The process ID. 303 304 Returns: 305 str: The username of the process owner. 306 """ 307 return self.username(self.piduser(pid)) 308 309 def pidtostr(self, pid): 310 """Converts a PID to a string. 311 312 Args: 313 pid (int): The process ID. 314 315 Returns: 316 str: The process ID as a string. 317 """ 318 return str(pid) 319 320 def pidname(self, pid): 321 """Gets the process name (from /proc/<pid>/comm). 322 323 Args: 324 pid (str): The process ID. 325 326 Returns: 327 str: The name of the process, or '?' on error. 328 """ 329 try: 330 return self.read("%s/comm" % pid)[:-1] 331 except Exception: 332 return "?"
Helper class to handle /proc/
179 def __init__(self, config: SmemConfig): 180 """Initializes ProcessData. 181 182 Args: 183 config (SmemConfig): The smem2 configuration. 184 """ 185 self._uidcache = UIDCache() 186 self.config = config
Initializes ProcessData.
Arguments:
- config (SmemConfig): The smem2 configuration.
213 def pids(self): 214 """Gets a list of process IDs. 215 216 Filters the list based on the configuration (e.g., specific PID). 217 Excludes kernel threads and the smem2 process itself if a filter is active. 218 219 Returns: 220 list[int]: A list of process IDs. 221 """ 222 return [ 223 int(e) 224 for e in self.listdir() 225 if e.isdigit() 226 and not self._iskernel(e) 227 and ((self.config.pid and self.config.pid == int(e)) or not self.config.pid) 228 and not ((int(e) == self.config.ownpid) and self.config.processfilter) 229 ] 230 # exclude own process when processfilter
Gets a list of process IDs.
Filters the list based on the configuration (e.g., specific PID). Excludes kernel threads and the smem2 process itself if a filter is active.
Returns:
list[int]: A list of process IDs.
232 def mapdata(self, pid): 233 """Gets the smaps data for a process. 234 235 It prefers using smaps_rollup for performance if available and configured. 236 237 Args: 238 pid (str): The process ID. 239 240 Returns: 241 list[str]: The lines from the smaps or smaps_rollup file, or an empty list on error. 242 """ 243 if self.config.rollup: 244 try: 245 return self.readlines( 246 "%s/smaps_rollup" % pid 247 ) # smaps_rollup new from kernel 4.14, at least 7x faster for non maping listing 248 except Exception: 249 pass 250 try: 251 return self.readlines("%s/smaps" % pid) 252 except (RuntimeError, PermissionError): 253 return []
Gets the smaps data for a process.
It prefers using smaps_rollup for performance if available and configured.
Arguments:
- pid (str): The process ID.
Returns:
list[str]: The lines from the smaps or smaps_rollup file, or an empty list on error.
255 def pidcmd(self, pid): 256 """Gets the command line for a process. 257 258 Args: 259 pid (str): The process ID. 260 261 Returns: 262 str: The command line of the process, or '?' on error. 263 """ 264 try: 265 c = self.read("%s/cmdline" % pid)[:-1] 266 c = c.replace("\0", " ") 267 if self.config.basename and not c == "": 268 c = os.path.basename(c.split()[0]) 269 return c 270 except Exception: 271 return "?"
Gets the command line for a process.
Arguments:
- pid (str): The process ID.
Returns:
str: The command line of the process, or '?' on error.
273 def piduser(self, pid): 274 """Gets the UID of the process owner. 275 276 Args: 277 pid (str): The process ID. 278 279 Returns: 280 int: The user ID of the process owner, or -1 on error. 281 """ 282 try: 283 return self._stat("%d" % pid).st_uid 284 except Exception: 285 return -1
Gets the UID of the process owner.
Arguments:
- pid (str): The process ID.
Returns:
int: The user ID of the process owner, or -1 on error.
287 def username(self, uid): 288 """Gets the username for a UID. 289 290 Args: 291 uid (int): The user ID. 292 293 Returns: 294 str: The username, or '?' if the UID is -1. 295 """ 296 return "?" if uid == -1 else self._uidcache(uid)
Gets the username for a UID.
Arguments:
- uid (int): The user ID.
Returns:
str: The username, or '?' if the UID is -1.
298 def pidusername(self, pid): 299 """Gets the username of the process owner. 300 301 Args: 302 pid (int or str): The process ID. 303 304 Returns: 305 str: The username of the process owner. 306 """ 307 return self.username(self.piduser(pid))
Gets the username of the process owner.
Arguments:
- pid (int or str): The process ID.
Returns:
str: The username of the process owner.
309 def pidtostr(self, pid): 310 """Converts a PID to a string. 311 312 Args: 313 pid (int): The process ID. 314 315 Returns: 316 str: The process ID as a string. 317 """ 318 return str(pid)
Converts a PID to a string.
Arguments:
- pid (int): The process ID.
Returns:
str: The process ID as a string.
320 def pidname(self, pid): 321 """Gets the process name (from /proc/<pid>/comm). 322 323 Args: 324 pid (str): The process ID. 325 326 Returns: 327 str: The name of the process, or '?' on error. 328 """ 329 try: 330 return self.read("%s/comm" % pid)[:-1] 331 except Exception: 332 return "?"
Gets the process name (from /proc/
Arguments:
- pid (str): The process ID.
Returns:
str: The name of the process, or '?' on error.
335def totalmem(config: SmemConfig): 336 """Calculates the total memory to be used as a reference for percentages. 337 338 It can be based on real memory from dmidecode, a user-provided value, 339 or the 'MemTotal' value from /proc/meminfo. 340 341 Args: 342 config (SmemConfig): The smem2 configuration. 343 344 Returns: 345 int: The total memory in kilobytes. 346 """ 347 if config.realmem: 348 return fromunits(config.realmem) / 1024 349 else: 350 if config.system or config.sysdetail: 351 try: 352 # only for systems running directly on HW 353 ram = ( 354 os.popen( 355 "dmidecode --type memory 2> /dev/null|awk '/Size: [0-9]+ MB/{sum+=$2};END{print sum}'", 356 ) 357 .read() 358 .strip() 359 ) 360 if ram.isdigit(): 361 return fromunits(str(int(ram)) + "M") / 1024 362 except Exception: 363 pass 364 365 return MemData()("memtotal")
Calculates the total memory to be used as a reference for percentages.
It can be based on real memory from dmidecode, a user-provided value, or the 'MemTotal' value from /proc/meminfo.
Arguments:
- config (SmemConfig): The smem2 configuration.
Returns:
int: The total memory in kilobytes.
368def pidmaps(pid, proc: ProcessData, config: SmemConfig, nomaps=False): 369 """Parses the memory maps for a given process. 370 371 It reads /proc/<pid>/smaps or /proc/<pid>/smaps_rollup to get memory 372 usage information for each mapping. 373 374 Args: 375 pid (int or str): The process ID. 376 proc (ProcessData): The ProcessData instance. 377 config (SmemConfig): The smem2 configuration. 378 nomaps (bool, optional): If True, only read the first mapping entry, 379 used for feature detection. Defaults to False. 380 381 Returns: 382 dict: A dictionary of memory mappings, where keys are start addresses. 383 """ 384 maps: dict = {} 385 start = None 386 status = None 387 if config.rollup or config.pssdetail or config.rssdetail: 388 try: 389 status = proc.readlines("%s/status" % pid) 390 except (RuntimeError, PermissionError): 391 status = None 392 393 for l in proc.mapdata(pid): 394 f = l.split() 395 if f[-1] == "kB": 396 maps[start][f[0][:-1].lower()] = int(f[1]) 397 elif "-" in f[0] and ":" not in f[0]: # looks like a mapping range 398 start, end = f[0].split("-") 399 start = int(start, 16) 400 name = "<anonymous>" 401 if len(f) > 5: 402 name = f[5] 403 if config.basename: 404 name = os.path.basename(name) 405 maps[start] = dict( 406 end=int(end, 16), 407 perm=f[1], 408 offset=int(f[2], 16), 409 major=int(f[3].split(":")[0], 16), 410 minor=int(f[3].split(":")[1], 16), 411 inode=int(f[4]), 412 name=name, 413 pss=0, 414 rss=0, 415 uss=0, 416 swap=0, 417 swappss=0, 418 pcent=0, 419 size=0, 420 ) 421 if config.pssdetail: 422 ( 423 maps[start]["pss_anon"], 424 maps[start]["pss_file"], 425 maps[start]["pss_shmem"], 426 ) = (0, 0, 0) 427 428 # for nomaps mode: stop reading mappings 429 if nomaps: 430 break 431 432 # add pss, rss, uss if available from smaps 433 if not config.rollup: 434 if not config.pssdetail: 435 for m in maps: 436 maps[m]["uss"] = maps[m].get("private_clean", 0) + maps[m].get( 437 "private_dirty", 0 438 ) 439 maps[m]["pss"] = maps[m]["uss"] + maps[m].get("shared_clean", 0) / 2 440 maps[m]["rss"] = maps[m].get("shared_clean", 0) + maps[m].get( 441 "shared_dirty", 0 442 ) 443 if config.rssdetail and status: 444 # from /proc/pid/status 445 for s in status: 446 if "RssAnon" in s: 447 maps[start]["rss_anon"] = int(s.split()[1]) 448 elif "RssFile" in s: 449 maps[start]["rss_file"] = int(s.split()[1]) 450 elif "RssShmem" in s: 451 maps[start]["rss_shmem"] = int(s.split()[1]) 452 453 # Parse VmSize for VSS calculation 454 if status: 455 for s in status: 456 if "VmSize:" in s: 457 # Set size for the last mapping (rollup case) or all mappings 458 if config.rollup and start: 459 maps[start]["size"] = int(s.split()[1]) 460 else: 461 # For non-rollup, distribute VmSize across all mappings 462 total_size = int(s.split()[1]) 463 if maps: 464 size_per_map = ( 465 total_size // len(maps) if len(maps) > 0 else 0 466 ) 467 for m in maps: 468 maps[m]["size"] = size_per_map 469 break 470 471 # Apply mapfilter if specified 472 if config.mapfilter and not nomaps: 473 f = {} 474 for m in maps: 475 if not filters(config.mapfilter, m, config, lambda x: maps[x]["name"]): 476 f[m] = maps[m] 477 return f 478 return maps
Parses the memory maps for a given process.
It reads /proc/
Arguments:
- pid (int or str): The process ID.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
- nomaps (bool, optional): If True, only read the first mapping entry, used for feature detection. Defaults to False.
Returns:
dict: A dictionary of memory mappings, where keys are start addresses.
481def maptotals(pids, proc: ProcessData, config: SmemConfig): 482 """Aggregates memory usage data for a list of pids, including map details. 483 484 For each process, it gets memory totals (PSS, RSS, USS, etc.) and 485 detailed mapping information. It applies process and user filters. 486 487 Args: 488 pids (list[int]): A list of process IDs. 489 proc (ProcessData): The ProcessData instance. 490 config (SmemConfig): The smem2 configuration. 491 492 Returns: 493 dict: A dictionary where keys are process IDs and values are dictionaries 494 containing memory totals and mapping details for each process. 495 """ 496 pt: dict = {} 497 for p in pids: 498 if filters( 499 config.processfilter, p, config, proc.pidcmd, proc.pidtostr, proc.pidname 500 ) or filters(config.userfilter, p, config, proc.pidusername): 501 continue 502 maps = pidmaps(p, proc, config) 503 if maps: 504 pt[p] = {} 505 pt[p]["maps"] = maps 506 totals = pidtotals(p, proc, config) 507 # Flatten the totals into the main dict for compatibility with display code 508 pt[p].update(totals) 509 return pt
Aggregates memory usage data for a list of pids, including map details.
For each process, it gets memory totals (PSS, RSS, USS, etc.) and detailed mapping information. It applies process and user filters.
Arguments:
- pids (list[int]): A list of process IDs.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary where keys are process IDs and values are dictionaries containing memory totals and mapping details for each process.
512def pidtotals(pid, proc: ProcessData, config: SmemConfig): 513 """Calculates the total memory usage (PSS, RSS, USS, etc.) for a single process. 514 515 It sums up the values from all memory mappings of the process. 516 517 Args: 518 pid (int or str): The process ID. 519 proc (ProcessData): The ProcessData instance. 520 config (SmemConfig): The smem2 configuration. 521 522 Returns: 523 dict: A dictionary with total PSS, RSS, USS, Swap, etc., for the process. 524 """ 525 totals: dict = {} 526 maps = pidmaps(pid, proc, config) 527 if maps: 528 maplist = maps.values() 529 totals["pss"] = sum([m.get("pss", 0) for m in maplist]) 530 totals["rss"] = sum([m.get("rss", 0) for m in maplist]) 531 private_clean = sum(m.get("private_clean", 0) for m in maplist) 532 private_dirty = sum(m.get("private_dirty", 0) for m in maplist) 533 totals["uss"] = private_clean + private_dirty 534 totals["swap"] = sum([m.get("swap", 0) for m in maplist]) 535 totals["size"] = sum([m.get("size", 0) for m in maplist]) 536 totals["maps"] = len(maps) 537 if config.swappss: 538 totals["swappss"] = sum([m.get("swappss", 0) for m in maplist]) 539 if config.pssdetail: 540 totals["pss_anon"] = sum([m.get("pss_anon", 0) for m in maplist]) 541 totals["pss_file"] = sum([m.get("pss_file", 0) for m in maplist]) 542 totals["pss_shmem"] = sum([m.get("pss_shmem", 0) for m in maplist]) 543 if config.rssdetail: 544 totals["rss_anon"] = sum([m.get("rss_anon", 0) for m in maplist]) 545 totals["rss_file"] = sum([m.get("rss_file", 0) for m in maplist]) 546 totals["rss_shmem"] = sum([m.get("rss_shmem", 0) for m in maplist]) 547 return totals
Calculates the total memory usage (PSS, RSS, USS, etc.) for a single process.
It sums up the values from all memory mappings of the process.
Arguments:
- pid (int or str): The process ID.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary with total PSS, RSS, USS, Swap, etc., for the process.
550def usertotals(pids, proc: ProcessData, config: SmemConfig): 551 """Aggregates memory usage by user. 552 553 It groups processes by user and calculates the total memory usage for each user. 554 555 Args: 556 pids (list[int]): A list of process IDs to analyze. 557 proc (ProcessData): The ProcessData instance. 558 config (SmemConfig): The smem2 configuration. 559 560 Returns: 561 dict: A dictionary where keys are user IDs and values are dictionaries 562 containing process lists and memory totals for each user. 563 """ 564 ut: dict = {} 565 for p in pids: 566 if filters( 567 config.processfilter, p, config, proc.pidcmd, proc.pidtostr, proc.pidname 568 ) or filters(config.userfilter, p, config, proc.pidusername): 569 continue 570 u = proc.piduser(p) 571 if u not in ut: 572 ut[u] = {"pids": [], "name": "?"} 573 ut[u]["name"] = proc.username(u) 574 ut[u]["pids"].append(p) 575 576 # get totals per user 577 for u in ut: 578 ut[u]["totals"] = processtotals(ut[u]["pids"], proc, config) 579 return ut
Aggregates memory usage by user.
It groups processes by user and calculates the total memory usage for each user.
Arguments:
- pids (list[int]): A list of process IDs to analyze.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary where keys are user IDs and values are dictionaries containing process lists and memory totals for each user.
582def cmdtotals(pids, proc: ProcessData, config: SmemConfig): 583 """Aggregates memory usage by command line. 584 585 It groups processes by their command line and calculates the total memory 586 usage for each command. 587 588 Args: 589 pids (list[int]): A list of process IDs to analyze. 590 proc (ProcessData): The ProcessData instance. 591 config (SmemConfig): The smem2 configuration. 592 593 Returns: 594 dict: A dictionary where keys are command lines and values are 595 dictionaries containing process lists and memory totals. 596 """ 597 # totals per command 598 ct: dict = {} 599 600 for p in pids: 601 if filters( 602 config.processfilter, p, config, proc.pidcmd, proc.pidtostr, proc.pidname 603 ) or filters(config.userfilter, p, config, proc.pidusername): 604 continue 605 cmdline = proc.pidcmd(p) 606 if not cmdline: 607 continue 608 609 parts = cmdline.split() 610 if not parts: 611 continue 612 c = parts[0] 613 614 # if multiple processes per command, then a list of pids 615 if c in ct: 616 ct[c]["pids"].append(p) 617 else: 618 # first pid for this command 619 ct[c] = {"pids": [p]} 620 621 # get totals per command 622 for c in ct: 623 ct[c]["totals"] = processtotals(ct[c]["pids"], proc, config) 624 if "totals" in ct[c] and ct[c]["totals"]: 625 ct[c]["name"] = c 626 ct[c]["user"] = proc.pidusername(ct[c]["pids"][0]) 627 628 # remove commands with empty totals 629 for c in list(ct.keys()): 630 if "totals" not in ct[c] or not ct[c]["totals"]: 631 del ct[c] 632 return ct
Aggregates memory usage by command line.
It groups processes by their command line and calculates the total memory usage for each command.
Arguments:
- pids (list[int]): A list of process IDs to analyze.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary where keys are command lines and values are dictionaries containing process lists and memory totals.
635def sortmaps(totals, key): 636 """Sorts a dictionary of totals by a given key. 637 638 Args: 639 totals (dict): A dictionary where values are another dictionary 640 containing a 'totals' key. 641 key (str): The key within the nested 'totals' dictionary to sort by. 642 643 Returns: 644 list: A sorted list of the input dictionary's values. 645 """ 646 # sort by key 647 return sorted(totals.values(), key=lambda x: x["totals"][key], reverse=True)
Sorts a dictionary of totals by a given key.
Arguments:
- totals (dict): A dictionary where values are another dictionary containing a 'totals' key.
- key (str): The key within the nested 'totals' dictionary to sort by.
Returns:
list: A sorted list of the input dictionary's values.
650def units(x): 651 """Converts a value in bytes to a human-readable string with units (K, M, G). 652 653 Args: 654 x (int or float): The value in bytes. 655 656 Returns: 657 str: A human-readable string representation of the value. 658 """ 659 if x > 1024 * 1024 * 1024: 660 return "%.1fG" % (x / (1024.0 * 1024 * 1024)) 661 if x > 1024 * 1024: 662 return "%.1fM" % (x / (1024.0 * 1024)) 663 if x > 1024: 664 return "%.1fK" % (x / 1024.0) 665 return str(x)
Converts a value in bytes to a human-readable string with units (K, M, G).
Arguments:
- x (int or float): The value in bytes.
Returns:
str: A human-readable string representation of the value.
668def fromunits(x): 669 """Converts a human-readable string with units (K, M, G, etc.) to bytes. 670 671 Args: 672 x (str): The string with a value and unit (e.g., "1024M"). 673 674 Returns: 675 int: The value in bytes. 676 677 Raises: 678 SystemExit: If the unit is not recognized. 679 """ 680 s = dict( 681 k=2**10, 682 K=2**10, 683 kB=2**10, 684 KB=2**10, 685 M=2**20, 686 MB=2**20, 687 G=2**30, 688 GB=2**30, 689 T=2**40, 690 TB=2**40, 691 ) 692 for k, v in list(s.items()): 693 if x.endswith(k): 694 return int(float(x[: -len(k)]) * v) 695 sys.stderr.write("Memory size should be written with units, for example 1024M\n") 696 sys.exit(-1)
Converts a human-readable string with units (K, M, G, etc.) to bytes.
Arguments:
- x (str): The string with a value and unit (e.g., "1024M").
Returns:
int: The value in bytes.
Raises:
- SystemExit: If the unit is not recognized.
699def processtotals(pids, proc: ProcessData, config: SmemConfig): 700 """Sums up memory usage totals for a list of processes. 701 702 Args: 703 pids (list[int]): The list of process IDs. 704 proc (ProcessData): The ProcessData instance. 705 config (SmemConfig): The smem2 configuration. 706 707 Returns: 708 dict: A dictionary with the summed PSS, RSS, USS, etc., for the 709 given processes. 710 """ 711 # Sums up totals of a pid list 712 t = {"uss": 0, "pss": 0, "rss": 0, "swap": 0, "size": 0} 713 if config.swappss: 714 t["swappss"] = 0 715 for p in pids: 716 pt = pidtotals(p, proc, config) 717 if pt: 718 for k in t: 719 if k in pt: 720 t[k] += pt[k] 721 return t
Sums up memory usage totals for a list of processes.
Arguments:
- pids (list[int]): The list of process IDs.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary with the summed PSS, RSS, USS, etc., for the given processes.
724def get_process_data(proc: ProcessData, config: SmemConfig): 725 """Collects and returns memory usage data for each process. 726 727 Args: 728 proc (ProcessData): The ProcessData instance. 729 config (SmemConfig): The smem2 configuration. 730 731 Returns: 732 dict: A dictionary containing memory data for each process, 733 as returned by maptotals. 734 """ 735 # Collect process data 736 pids = proc.pids() 737 data = maptotals(pids, proc, config) 738 return data
Collects and returns memory usage data for each process.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary containing memory data for each process, as returned by maptotals.
741def get_map_data(proc: ProcessData, config: SmemConfig): 742 """Collects and returns memory usage data aggregated by mapping name. 743 744 Args: 745 proc (ProcessData): The ProcessData instance. 746 config (SmemConfig): The smem2 configuration. 747 748 Returns: 749 dict: A dictionary containing memory data aggregated by mapping name. 750 """ 751 # Collect map data - aggregate by mapping name 752 pids = proc.pids() 753 data = mapnametotals(pids, proc, config) 754 return data
Collects and returns memory usage data aggregated by mapping name.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary containing memory data aggregated by mapping name.
757def mapnametotals(pids, proc: ProcessData, config: SmemConfig): 758 """Aggregates memory usage by mapping name across a list of processes. 759 760 It iterates through processes and their memory maps, summing up memory 761 statistics for each unique mapping name. 762 763 Args: 764 pids (list[int]): A list of process IDs. 765 proc (ProcessData): The ProcessData instance. 766 config (SmemConfig): The smem2 configuration. 767 768 Returns: 769 dict: A dictionary where keys are mapping names and values are 770 dictionaries of aggregated memory statistics. 771 """ 772 totals: dict = {} 773 for pid in pids: 774 # Apply filters like the original version 775 if filters( 776 config.processfilter, pid, config, proc.pidcmd, proc.pidtostr, proc.pidname 777 ) or filters(config.userfilter, pid, config, proc.pidusername): 778 continue 779 try: 780 maps = pidmaps(pid, proc, config) 781 seen = {} 782 for m in list(maps.keys()): 783 name = maps[m]["name"] 784 if name not in totals: 785 t = dict( 786 size=0, 787 rss=0, 788 pss=0, 789 shared_clean=0, 790 shared_dirty=0, 791 private_clean=0, 792 count=0, 793 private_dirty=0, 794 referenced=0, 795 swap=0, 796 pids=0, 797 ) 798 if config.swappss: 799 t["swappss"] = 0 800 else: 801 t = totals[name] 802 for k in t: 803 t[k] += maps[m].get(k, 0) 804 t["count"] += 1 805 if name not in seen: 806 t["pids"] += 1 807 seen[name] = 1 808 totals[name] = t 809 except (OSError, IOError): 810 continue 811 return totals
Aggregates memory usage by mapping name across a list of processes.
It iterates through processes and their memory maps, summing up memory statistics for each unique mapping name.
Arguments:
- pids (list[int]): A list of process IDs.
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary where keys are mapping names and values are dictionaries of aggregated memory statistics.
814def get_user_data(proc: ProcessData, config: SmemConfig): 815 """Collects and returns memory usage data aggregated by user. 816 817 Args: 818 proc (ProcessData): The ProcessData instance. 819 config (SmemConfig): The smem2 configuration. 820 821 Returns: 822 dict: A dictionary containing memory data aggregated by user. 823 """ 824 # Collect user data 825 pids = proc.pids() 826 data = usertotals(pids, proc, config) 827 return data
Collects and returns memory usage data aggregated by user.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary containing memory data aggregated by user.
830def get_cmd_data(proc: ProcessData, config: SmemConfig): 831 """Collects and returns memory usage data aggregated by command. 832 833 Args: 834 proc (ProcessData): The ProcessData instance. 835 config (SmemConfig): The smem2 configuration. 836 837 Returns: 838 dict: A dictionary containing memory data aggregated by command. 839 """ 840 # Collect cmd data 841 pids = proc.pids() 842 data = cmdtotals(pids, proc, config) 843 return data
Collects and returns memory usage data aggregated by command.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
dict: A dictionary containing memory data aggregated by command.
846def get_system_data(proc: ProcessData, config: SmemConfig): 847 """Collects and returns system-wide memory usage data. 848 849 This provides a breakdown of memory usage by different system components 850 like kernel, firmware, userspace, etc. 851 852 Args: 853 proc (ProcessData): The ProcessData instance. 854 config (SmemConfig): The smem2 configuration. 855 856 Returns: 857 list[tuple]: A list of tuples, where each tuple represents a memory 858 area and its usage in PSS and RSS. 859 """ 860 t = totalmem(config) 861 m = MemData() 862 mt = m("memtotal") 863 f = m("memfree") 864 kernel = kernelsize(config) 865 866 # total amount mapped into userspace (ie mapped an unmapped pages) /dev/zero mappings 867 u = m("anonpages") + m("mapped") 868 869 # total amount used by hardware 870 fh = int(max(t - mt - kernel, 0)) 871 872 # total amount allocated by kernel not for userspace 873 kd = mt - f - u 874 875 # total amount in kernel caches 876 kdc = m("buffers") + m("sreclaimable") + m("cached") - m("mapped") 877 878 if config.sysdetail: 879 shmp = mapshared(proc, config) # shared memory mapped by processes 880 mapzero = mapdevzero( 881 proc, config 882 ) # dev/zero mapped as files should be subtracted from files Mapped from userspace 883 mapped = m("mapped") - mapzero 884 modules = kernelmodsize(proc, config) 885 shm = m("shmem") - shmp 886 887 mounts = proc.readlines("mounts") 888 ramfs = 0 889 for r in mounts: 890 if "ramfs" in r: 891 try: 892 ramfs += int( 893 (os.popen("du -sk %s" % r.split()[1]).read()).split()[0] 894 ) 895 except Exception: 896 pass 897 filecache = m("cached") - mapped - shm - ramfs 898 unknown = ( 899 mt 900 - f 901 - u 902 - ( 903 modules 904 + m("pagetables") 905 + m("kernelstack") 906 + m("slab") 907 + m("buffers") 908 + filecache 909 + shm 910 + ramfs 911 ) 912 ) 913 if unknown < 0: 914 unknown = 0 915 916 lines: list[tuple] 917 if config.sysdetail: 918 lines = [ 919 ("firmware/hardware", fh, 0, 0), 920 ("kernel image", kernel, 0, 0), 921 ("kernel modules", modules, 0, 0), 922 ("page tables", m("pagetables"), 0, 0), 923 ("kernel stack", m("kernelstack"), 0, 0), 924 ("slab (all/SReclaimable)", m("slab"), m("sreclaimable"), 0), 925 ("buffers", m("buffers"), m("buffers"), 0), 926 ("cached (w/o mapped,tmpfs,ramfs)", filecache, filecache, 0), 927 ("shared (non process tmpfs)", shm, 0, 0), 928 ("ramfs", ramfs, 0, 0), 929 ("unknown", unknown, 0, 0), 930 ("processes (all/mapped files)", u, mapped, 0), 931 ("free memory", f, f, 0), 932 ] 933 if "all" in config.columns or "details" in config.columns: 934 line_all: list[tuple] = [ 935 ("-------------------------------", 0, 0, 0), 936 ("/dev/zero mapped", 0, 0, mapzero), 937 ("shared by processes", 0, 0, shmp), 938 ("unevictable", 0, 0, m("unevictable")), 939 ("dirty (unwritten to file)", 0, 0, m("dirty")), 940 ("swapped", 0, 0, m("swaptotal") - m("swapfree")), 941 ("available (estimated)", 0, 0, m("memavailable")), 942 ] 943 lines = lines + line_all 944 else: 945 lines = [ 946 ("firmware/hardware", fh, 0), 947 ("kernel image", kernel, 0), 948 ("kernel dynamic memory", kd, kdc), 949 ("userspace memory", u, m("mapped")), 950 ("free memory", f, f), 951 ] 952 return lines
Collects and returns system-wide memory usage data.
This provides a breakdown of memory usage by different system components like kernel, firmware, userspace, etc.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
list[tuple]: A list of tuples, where each tuple represents a memory area and its usage in PSS and RSS.
955def mapdevzero(proc: ProcessData, config: SmemConfig): 956 """Calculates memory mapped to /dev/zero. 957 958 Some Linux versions count memory mapped to /dev/zero as 'Mapped' instead 959 of 'AnonPages'. This function calculates that amount. 960 961 Args: 962 proc (ProcessData): The ProcessData instance. 963 config (SmemConfig): The smem2 configuration. 964 965 Returns: 966 int: Total PSS of memory mapped to /dev/zero, in kilobytes. 967 """ 968 ps = proc.pids() 969 config.mapfilter = "^/dev/zero$" 970 pt = maptotals(ps, proc, config) 971 t = 0.0 972 for r in pt: 973 t += pt[r]["pss"] 974 return int(t)
Calculates memory mapped to /dev/zero.
Some Linux versions count memory mapped to /dev/zero as 'Mapped' instead of 'AnonPages'. This function calculates that amount.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
int: Total PSS of memory mapped to /dev/zero, in kilobytes.
996def kernelsize(config: SmemConfig): 997 """Determines the size of the kernel image from the file specified. 998 999 Args: 1000 config (SmemConfig): The smem2 configuration. 1001 1002 Returns: 1003 int: The kernel image size in kilobytes. 1004 """ 1005 kernelsize_val = 0 1006 if config.kernel: 1007 try: 1008 d = os.popen("size %s" % config.kernel).readlines()[1].split() 1009 if int(d[1]) == 0: # data part missing, seems like packed file 1010 # try some heuristic to find gzipped part in kernel image 1011 with open(config.kernel, "rb") as f: 1012 packedkernel = f.read() 1013 pos = packedkernel.find(b"\x1f\x8b") 1014 if pos >= 0 and pos < 25000: 1015 if not config.quiet: 1016 sys.stderr.write( 1017 "Parameter '%s' should be an original uncompressed compiled kernel file.\n" 1018 % config.kernel 1019 ) 1020 sys.stderr.write( 1021 "Maybe uncompressed kernel can be extracted by the command:\n" 1022 " dd if=%s bs=1 skip=%d | gzip -d >%s.unpacked\n\n" 1023 % (config.kernel, pos, config.kernel) 1024 ) 1025 else: 1026 kernelsize_val = int(int(d[3]) / 1024 + 0.5) 1027 except Exception: 1028 pass # Fail silently if size command fails or kernel file is not found 1029 return kernelsize_val
Determines the size of the kernel image from the file specified.
Arguments:
- config (SmemConfig): The smem2 configuration.
Returns:
int: The kernel image size in kilobytes.
1032def kernelmodsize(proc: ProcessData, config: SmemConfig): 1033 """Calculates the total size of loaded kernel modules. 1034 1035 It reads /proc/modules to get the size of each module. 1036 1037 Args: 1038 proc (ProcessData): The ProcessData instance. 1039 config (SmemConfig): The smem2 configuration. 1040 1041 Returns: 1042 int: The total size of kernel modules in kilobytes. 1043 """ 1044 ms = 0.0 1045 try: 1046 md = proc.readlines("modules") 1047 for m in md: 1048 ms += int(m.split()[1]) 1049 ms /= 1024 1050 except Exception: 1051 if not config.quiet: 1052 sys.stderr.write("Detection of kernel modules size failed\n") 1053 return int(ms)
Calculates the total size of loaded kernel modules.
It reads /proc/modules to get the size of each module.
Arguments:
- proc (ProcessData): The ProcessData instance.
- config (SmemConfig): The smem2 configuration.
Returns:
int: The total size of kernel modules in kilobytes.
1056def setdatasources(config: SmemConfig, proc: ProcessData): 1057 """Detects kernel features and updates the configuration accordingly. 1058 1059 It checks for support of PSS, SwapPSS, and detailed PSS/RSS reporting 1060 by inspecting the smaps of the current process. It also determines if 1061 the faster smaps_rollup can be used. 1062 1063 Args: 1064 config (SmemConfig): The smem2 configuration object to update. 1065 proc (ProcessData): The ProcessData instance. 1066 1067 Returns: 1068 SmemConfig: The updated configuration object. 1069 """ 1070 """New kernel features detection""" 1071 config.ownpid = os.getpid() 1072 1073 config.rssdetail = False 1074 config.pssdetail = True 1075 config.rollup = True 1076 rd = proc.readlines("%s/status" % config.ownpid) 1077 for r in rd: 1078 if "RssAnon:" in r: 1079 config.rssdetail = True 1080 break 1081 1082 map_data = pidmaps(config.ownpid, proc, config, nomaps=True) 1083 map_data = map_data[list(map_data.keys())[0]] 1084 1085 if "pss" not in map_data and not config.quiet: 1086 sys.stderr.write("Warning: Kernel does not appear to support PSS measurement\n") 1087 1088 if "swappss" in map_data: 1089 config.swappss = True 1090 else: 1091 config.swappss = False 1092 if not config.quiet: 1093 sys.stderr.write( 1094 "Warning: Kernel does not appear to support SwapPSS measurement\n" 1095 ) 1096 1097 if "pss_anon" in map_data: 1098 config.pssdetail = True 1099 else: 1100 config.pssdetail = False 1101 if not config.quiet: 1102 sys.stderr.write( 1103 "Warning: Kernel does not appear to support Pss Anon/File/Shmem measurement\n" 1104 ) 1105 1106 """ some smem2 features need full smaps""" 1107 if ( 1108 config.mapfilter 1109 or config.mappings 1110 or "maps" in config.columns 1111 or ("all" in config.columns and not config.groupcmd) 1112 ): 1113 config.rollup = False 1114 return config
Detects kernel features and updates the configuration accordingly.
It checks for support of PSS, SwapPSS, and detailed PSS/RSS reporting by inspecting the smaps of the current process. It also determines if the faster smaps_rollup can be used.
Arguments:
- config (SmemConfig): The smem2 configuration object to update.
- proc (ProcessData): The ProcessData instance.
Returns:
SmemConfig: The updated configuration object.
1117def filters(opt, arg, config: SmemConfig, *sources): 1118 """A generic filter function based on regular expressions. 1119 1120 It checks if a given argument should be filtered out by matching a regex 1121 against one or more source strings. 1122 1123 Args: 1124 opt (str or None): The regex pattern to search for. If None or empty, 1125 the function returns False. 1126 arg: The argument to pass to the source functions (e.g., a PID). 1127 config (SmemConfig): The smem2 configuration (for ignorecase flag). 1128 *sources (callable): Variable length list of functions that take `arg` 1129 and return a string to be searched. 1130 1131 Returns: 1132 bool: True if the item should be filtered out (no match found), 1133 False otherwise (a match was found or no filter is active). 1134 """ 1135 if not opt: 1136 return False 1137 for f in sources: 1138 if re.search(opt, f(arg), re.I if config.ignorecase else 0): 1139 return False 1140 return True
A generic filter function based on regular expressions.
It checks if a given argument should be filtered out by matching a regex against one or more source strings.
Arguments:
- opt (str or None): The regex pattern to search for. If None or empty, the function returns False.
- arg: The argument to pass to the source functions (e.g., a PID).
- config (SmemConfig): The smem2 configuration (for ignorecase flag).
- *sources (callable): Variable length list of functions that take
argand return a string to be searched.
Returns:
bool: True if the item should be filtered out (no match found), False otherwise (a match was found or no filter is active).