Skip to content

Instantly share code, notes, and snippets.

@iTrooz
Created July 30, 2024 11:07
Show Gist options
  • Save iTrooz/1d7cbefedd994ae4719fc08f35a649cc to your computer and use it in GitHub Desktop.
Save iTrooz/1d7cbefedd994ae4719fc08f35a649cc to your computer and use it in GitHub Desktop.
pacman package upgrade size verbose
#!/usr/bin/env python
"""
This script shows the individual size changes of upgradable packages in Arch Linux.
You can use it to understand, e.g. why your upgrade size is negative.
"""
import typing
import subprocess
import sys
# ---- UNIT FUNCTIONS
def get_unit_weight(unit: str) -> int:
match unit:
case 'B':
return 1
case 'KiB':
return 1024
case 'MiB':
return 1024 * 1024
case 'GiB':
return 1024 * 1024 * 1024
case 'TiB':
return 1024 * 1024 * 1024 * 1024
case _:
raise ValueError(f"Unknown unit: {unit}")
# From Github Copilot
def convert_to_human_size(size: float) -> str:
if size < 0:
return f"-{convert_to_human_size(-size)}"
units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
unit_index = 0
while size >= 1024 and unit_index < len(units) - 1:
size /= 1024
unit_index += 1
return f"{size:.2f} {units[unit_index]}"
# Parse a text in the form 2,5 MiB and returns bytes
def parse_human_size(size_str: str) -> float:
size_str = size_str.replace(',', '.')
num, unit = size_str.split(' ')
return float(num) * get_unit_weight(unit)
# ----- PACMAN PARSING FUNCTIONS
# Get upgradable packages
def get_wanted_packages():
output = subprocess.check_output(['pacman', '-Qu']).decode('utf-8')
output = output.strip().split('\n')
packages = list(map(lambda x: x.split(' ')[0], output))
return packages
def parse_installed_sizes(output: str) -> dict[str, float]:
def get_value(line: str) -> str:
return line.split(':')[1].strip()
sizes = {}
current_name = None
for line in output.split('\n'):
if line.startswith('Name'):
current_name = get_value(line)
if line.startswith('Installed Size'):
size_str = get_value(line)
size = parse_human_size(size_str)
sizes[current_name] = size
return sizes
def get_cmd_output(cmd: list[str]) -> str:
try:
return subprocess.check_output([*cmd], stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError as e:
return e.output
def get_packages_size(packages: list[str]) -> typing.Union[float, float] | None:
sync_output = get_cmd_output(['pacman', '-Si', *packages]).decode('utf-8')
sync_sizes = parse_installed_sizes(sync_output)
installed_output = get_cmd_output(['pacman', '-Qi', *packages]).decode('utf-8')
local_sizes = parse_installed_sizes(installed_output)
return local_sizes, sync_sizes
# ----- MAIN
def compare_package_sizes():
packages = get_wanted_packages()
total_delta = 0
deltas = {}
local_sizes, sync_sizes = get_packages_size(packages)
for package, local_size in local_sizes.items():
if package not in sync_sizes:
print(f"Package '{package}' not found in sync database. Probably an AUR/locally installed package.", file=sys.stderr)
continue
sync_size = sync_sizes[package]
delta = sync_size - local_size
total_delta += delta
deltas[package] = delta
if len(sys.argv) == 1:
s = sorted(deltas.items(), key=lambda x: x[1])
elif len(sys.argv) == 2:
if sys.argv[1] == "reverse":
s = sorted(deltas.items(), key=lambda x: x[1], reverse=True)
else:
raise ValueError(f"Unknown argument: {sys.argv[1]}")
else:
raise ValueError("Too many arguments")
for package, delta in s:
print(f"{package} size change: {convert_to_human_size(delta)}")
print(f"Total install size delta: {convert_to_human_size(total_delta)}")
compare_package_sizes()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment