commit 1099616a4e5a172fbca60c46d6a59ad73bef630c Author: Andrey Golikov Date: Wed Nov 5 14:59:52 2025 +0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..d70c9f7 --- /dev/null +++ b/main.py @@ -0,0 +1,85 @@ +from dataclasses import dataclass +import dataclasses +from decimal import Decimal +from functools import reduce +from typing import NamedTuple, Optional +from line_profiler import profile +import psutil + +@dataclass +class ProcessSwapInfo: + pid: int + name: str + swap: int + +@dataclass +class ProcessSwapGroup: + name: str + processes: list[ProcessSwapInfo] = dataclasses.field(default_factory=list) + + @property + def total_swap(self) -> int: + return sum(map(lambda x: x.swap, self.processes)) + +type ProcessName = str + +def convert_bytes_to_mb(value: int) -> Decimal: + return Decimal(value) / (1024 * 1024) + +@profile +def get_process_swap_usage() -> None: + print(f"{'PID':>6} {'Name':<25} {'Swap (MB)':>10}") + print("-" * 45) + + total_swap_used = 0 + + swap_used: list[ProcessSwapInfo] = [] + + for proc in psutil.process_iter(['pid', 'name', 'memory_full_info']): + try: + with proc.oneshot(): + memory_info: Optional[NamedTuple] = proc.info['memory_full_info'] + swap: int = getattr(memory_info, "swap", 0) + if swap > 0: + swap_used.append(ProcessSwapInfo(proc.pid, proc.name(), swap)) + total_swap_used += swap + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + swap_used.sort(key=lambda x: x.swap) + + def _add_to_group(groups: dict[ProcessName, ProcessSwapGroup], process: ProcessSwapInfo) -> dict[ProcessName, ProcessSwapGroup]: + if process.name not in groups: + groups[process.name] = ProcessSwapGroup(process.name) + groups[process.name].processes.append(process) + return groups + + swap_used_groups: list[ProcessSwapGroup] = sorted( + (item for item in reduce(_add_to_group, swap_used, dict()).values()), + key=lambda x: x.total_swap + ) + + for group in swap_used_groups: + print(f"{group.name:<32} {convert_bytes_to_mb(group.total_swap):10.2f}") + for item in group.processes: + print(f"{item.pid:>32} {convert_bytes_to_mb(item.swap):10.2f}") + print() + + print("-" * 45) + print(f"{'Total swap used by processes:':<32} {convert_bytes_to_mb(total_swap_used):10.2f} MB") + +def show_swap_summary() -> None: + swap = psutil.swap_memory() + print("\n=== Swap Summary ===") + print(f"Total: {convert_bytes_to_mb(swap.total):.2f} MB") + print(f"Used: {convert_bytes_to_mb(swap.used):.2f} MB") + print(f"Free: {convert_bytes_to_mb(swap.free):.2f} MB") + print(f"Percent Used: {swap.percent:.1f}%") + +def main() -> None: + print("=== Process Swap Usage ===") + get_process_swap_usage() + show_swap_summary() + +if __name__ == "__main__": + main() diff --git a/profile.txt b/profile.txt new file mode 100644 index 0000000..2482145 --- /dev/null +++ b/profile.txt @@ -0,0 +1,51 @@ +Wrote profile results to 'main.py.lprof' +Timer unit: 1e-06 s + +Total time: 0.465667 s +File: main.py +Function: get_process_swap_usage at line 29 + +Line # Hits Time Per Hit % Time Line Contents +============================================================== + 29 @profile + 30 def get_process_swap_usage() -> None: + 31 1 4.7 4.7 0.0 print(f"{'PID':>6} {'Name':<25} {'Swap (MB)':>10}") + 32 1 1.9 1.9 0.0 print("-" * 45) + 33 + 34 1 1.5 1.5 0.0 total_swap_used = 0 + 35 + 36 1 1.5 1.5 0.0 swap_used: list[ProcessSwapInfo] = [] + 37 + 38 476 428514.1 900.2 92.0 for proc in psutil.process_iter(['pid', 'name', 'memory_full_info']): + 39 475 894.0 1.9 0.2 try: + 40 950 19757.6 20.8 4.2 with proc.oneshot(): + 41 475 878.1 1.8 0.2 mem_info = proc.info['memory_full_info'] + 42 475 942.4 2.0 0.2 s: int = getattr(mem_info, "swap", 0) + 43 475 880.2 1.9 0.2 if s > 0: + 44 167 10376.3 62.1 2.2 swap_used.append(ProcessSwapInfo(proc.pid, proc.name(), s)) + 45 167 320.2 1.9 0.1 total_swap_used += s + 46 except (psutil.NoSuchProcess, psutil.AccessDenied): + 47 continue + 48 + 49 1 97.1 97.1 0.0 swap_used.sort(key=lambda x: x.swap) + 50 + 51 1 2.4 2.4 0.0 def add_to_group(groups: dict[ProcessName, ProcessSwapGroup], process: ProcessSwapInfo) -> dict[ProcessName, ProcessSwapGroup]: + 52 if process.name not in groups: + 53 groups[process.name] = ProcessSwapGroup(process.name) + 54 groups[process.name].processes.append(process) + 55 return groups + 56 + 57 2 247.9 124.0 0.1 swap_used_groups: list[ProcessSwapGroup] = sorted( + 58 1 548.6 548.6 0.1 (item for item in reduce(add_to_group, swap_used, dict()).values()), + 59 1 2.4 2.4 0.0 key=lambda x: x.total_swap + 60 ) + 61 + 62 103 191.7 1.9 0.0 for group in swap_used_groups: + 63 102 617.0 6.0 0.1 print(f"{group.name:<32} {bytes_to_mb(group.total_swap):10.2f}") + 64 269 489.7 1.8 0.1 for item in group.processes: + 65 167 679.6 4.1 0.1 print(f"{item.pid:>32} {bytes_to_mb(item.swap):10.2f}") + 66 102 210.4 2.1 0.0 print() + 67 + 68 1 2.4 2.4 0.0 print("-" * 45) + 69 1 5.2 5.2 0.0 print(f"{'Total swap used by processes:':<32} {bytes_to_mb(total_swap_used):10.2f} MB") + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fd194a1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "swap" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "psutil>=7.1.3", +] + +[dependency-groups] +dev = [ + "line-profiler>=5.0.0", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1be9c87 --- /dev/null +++ b/uv.lock @@ -0,0 +1,48 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "line-profiler" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5c/bbe9042ef5cf4c6cad4bf4d6f7975193430eba9191b7278ea114a3993fbb/line_profiler-5.0.0.tar.gz", hash = "sha256:a80f0afb05ba0d275d9dddc5ff97eab637471167ff3e66dcc7d135755059398c", size = 376919, upload-time = "2025-07-23T20:15:41.819Z" } + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "swap" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "psutil" }, +] + +[package.dev-dependencies] +dev = [ + { name = "line-profiler" }, +] + +[package.metadata] +requires-dist = [{ name = "psutil", specifier = ">=7.1.3" }] + +[package.metadata.requires-dev] +dev = [{ name = "line-profiler", specifier = ">=5.0.0" }]