|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -eu |
| 3 | + |
| 4 | +# The path to the directory that holds this script |
| 5 | +CURRENT_SCRIPT_DIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" |
| 6 | +BASE_DEBUG_DIR="/tmp/debug-initramfs" |
| 7 | +mkdir -p ${BASE_DEBUG_DIR} |
| 8 | +BASE_HELPER_FILE_DIR=$(sudo mktemp -dp "${BASE_DEBUG_DIR}") |
| 9 | + |
| 10 | +# Fix permissions so current user can create temp files |
| 11 | +sudo chown "${USER}:$(id -gn)" "${BASE_HELPER_FILE_DIR}" |
| 12 | + |
| 13 | +# Set up cleanup trap to run when script exits |
| 14 | +trap clean_up_debug_helpers EXIT |
| 15 | + |
| 16 | +# set_up_debug_dirs [file] [url] |
| 17 | +# |
| 18 | +# This ceates a minimal set up in /tmp/debug-initramfs directory for |
| 19 | +# comparing built ipa initramfs the master ipa initramfs. |
| 20 | +# |
| 21 | +# Usage examples: |
| 22 | +# ./ipa_debug_tools.sh set_up_debug_dirs |
| 23 | +# ./ipa_debug_tools.sh set_up_debug_dirs ipa-centos10-master.tar.gz |
| 24 | +# ./ipa_debug_tools.sh set_up_debug_dirs ipa-centos10-master.tar.gz https://tarballs.opendev.org/openstack/ironic-python-agent/dib/ |
| 25 | +# |
| 26 | +# - [file]: Name of tar file o be downloaded [default: ipa-centos10-master.tar.gz] |
| 27 | +# - [url]: Url to ironic python agent tarball archive [default: https://tarballs.opendev.org/openstack/ironic-python-agent/dib/] |
| 28 | +# |
| 29 | +set_up_debug_dirs() { |
| 30 | + file_name="${1:-ipa-centos9-master.tar.gz}" |
| 31 | + url="${2:-https://tarballs.opendev.org/openstack/ironic-python-agent/dib/}" |
| 32 | + full_path="${url}${file_name}" |
| 33 | + file_base_name=$(echo "${file_name}"| cut -d'.' -f 1) |
| 34 | + |
| 35 | + clean_up_debug_dirs |
| 36 | + |
| 37 | + # Export DISABLE_UPLOAD, ENABLE_BOOTSTRAP_TEST, TEST_IN_CI to allow local |
| 38 | + # test run. |
| 39 | + export DISABLE_UPLOAD="true" |
| 40 | + export ENABLE_BOOTSTRAP_TEST="false" |
| 41 | + export TEST_IN_CI="false" |
| 42 | + |
| 43 | + # Build ipa with build_ipa.sh. Search for the script in a path that is |
| 44 | + # relative to this files location. |
| 45 | + cd "${CURRENT_SCRIPT_DIR}/../jenkins/scripts/dynamic_worker_workflow" |
| 46 | + if [[ ! -x ./build_ipa.sh ]]; then |
| 47 | + echo "Error: build_ipa.sh not found" |
| 48 | + exit 1 |
| 49 | + fi |
| 50 | + |
| 51 | + if ! ./build_ipa.sh; then |
| 52 | + echo "build_ipa.sh failed, will check for artifact..." |
| 53 | + fi |
| 54 | + |
| 55 | + # Even if build_ipa.sh exits with error this script can continue as long as |
| 56 | + # /tmp/dib/ironic-python-agent.initramfs has been created |
| 57 | + if [[ ! -f /tmp/dib/ironic-python-agent.initramfs ]]; then |
| 58 | + echo "Required file '/tmp/dib/ironic-python-agent.initramfs' not created, exiting." |
| 59 | + exit 1 |
| 60 | + fi |
| 61 | + |
| 62 | + # Unzip ironic-python-agent.initramfs |
| 63 | + sudo mkdir -p "${BASE_DEBUG_DIR}/build-ipa-initramfs" |
| 64 | + sudo cp /tmp/dib/ironic-python-agent.initramfs "${BASE_DEBUG_DIR}/" |
| 65 | + cd "${BASE_DEBUG_DIR}/build-ipa-initramfs" |
| 66 | + gunzip -c ../ironic-python-agent.initramfs | sudo cpio -id |
| 67 | + |
| 68 | + # Get master ironic-python-agent.initramfs and unzip |
| 69 | + sudo mkdir -p "${BASE_DEBUG_DIR}/master-ipa-initramfs" |
| 70 | + cd "${BASE_DEBUG_DIR}" |
| 71 | + sudo wget "${full_path}" |
| 72 | + sudo tar -xzf "${file_name}" |
| 73 | + cd master-ipa-initramfs |
| 74 | + gunzip -c "../${file_base_name}.initramfs" | sudo cpio -id |
| 75 | +} |
| 76 | + |
| 77 | +# ----------------------------------------------------------------------------- |
| 78 | +# compare_dir_sizes <relative_path> [base_build_dir] [base_master_dir] |
| 79 | +# |
| 80 | +# Compares the sizes of files and directories at the given <relative_path> |
| 81 | +# inside two extracted initramfs directories (or any two directories). |
| 82 | +# |
| 83 | +# Usage examples: |
| 84 | +# ./ipa_debug_tools.sh compare_dir_sizes |
| 85 | +# ./ipa_debug_tools.sh compare_dir_sizes usr |
| 86 | +# ./ipa_debug_tools.sh compare_dir_sizes usr/lib ~/ipa-build-initramfs ~/ipa-initramfs |
| 87 | +# ./ipa_debug_tools.sh compare_dir_sizes . ~/ipa-build-initramfs ~/ipa-initramfs |
| 88 | +# |
| 89 | +# - <relative_path>: Path inside the compared directories (e.g., usr, usr/lib) |
| 90 | +# - [base_build_dir]: First base directory (default: /tmp/debug-initramfs/build-ipa-initramfs) |
| 91 | +# - [base_master_dir]: Second base directory (default: /tmp/debug-initramfs/master-ipa-initramfs) |
| 92 | +# |
| 93 | +# The function prints a table of entries, their sizes in both directories, |
| 94 | +# and the size difference. It also lists files only present in one directory. |
| 95 | +# ----------------------------------------------------------------------------- |
| 96 | +compare_dir_sizes() { |
| 97 | + path="${1:-}" |
| 98 | + build_dir_base="${2:-${BASE_DEBUG_DIR}/build-ipa-initramfs}" |
| 99 | + master_dir_base="${3:-${BASE_DEBUG_DIR}/master-ipa-initramfs}" |
| 100 | + |
| 101 | + build_dir="${build_dir_base}/${path}" |
| 102 | + master_dir="${master_dir_base}/${path}" |
| 103 | + |
| 104 | + # Color codes |
| 105 | + RED='\033[0;31m' |
| 106 | + GREEN='\033[0;32m' |
| 107 | + YELLOW='\033[1;33m' |
| 108 | + BLUE='\033[0;34m' |
| 109 | + PURPLE='\033[0;35m' |
| 110 | + CYAN='\033[0;36m' |
| 111 | + NC='\033[0m' # No Color |
| 112 | + BOLD='\033[1m' |
| 113 | + |
| 114 | + echo -e "${CYAN}Comparing directories:${NC}" |
| 115 | + echo -e " ${BLUE}Build IPA:${NC} ${build_dir}" |
| 116 | + echo -e " ${GREEN}Master IPA:${NC} ${master_dir}" |
| 117 | + echo "" |
| 118 | + |
| 119 | + # Print table header |
| 120 | + printf "${BOLD}%-40s %-22s %-22s %-22s${NC}\n" "Directory/File" "Build" "Master" "Diff" |
| 121 | + echo "=======================================================================================================" |
| 122 | + |
| 123 | + tmpfile=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 124 | + build_dir_list=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 125 | + master_dir_list=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 126 | + checked=0 |
| 127 | + skipped=0 |
| 128 | + |
| 129 | + # Compare entries present in both directories |
| 130 | + find "${build_dir}" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort > "${build_dir_list}" |
| 131 | + find "${master_dir}" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort > "${master_dir_list}" |
| 132 | + comm -12 "${build_dir_list}" "${master_dir_list}" | while IFS= read -r name; do |
| 133 | + # Get size in bytes for each entry |
| 134 | + bsize=$(du -sb "${build_dir}/${name}" 2>/dev/null | cut -f1) |
| 135 | + isize=$(du -sb "${master_dir}/${name}" 2>/dev/null | cut -f1) |
| 136 | + # Calculate absolute difference |
| 137 | + diff=$(( bsize > isize ? bsize - isize : isize - bsize )) |
| 138 | + echo -e "${name}\t${bsize}\t${isize}\t${diff}" |
| 139 | + done | sort -k4,4nr > "${tmpfile}" |
| 140 | + |
| 141 | + # Print entries with size differences |
| 142 | + while IFS=$'\t' read -r name bsize isize diff; do |
| 143 | + checked=$((checked + 1)) |
| 144 | + if [[ "${diff}" -eq 0 ]]; then |
| 145 | + skipped=$((skipped + 1)) |
| 146 | + continue |
| 147 | + fi |
| 148 | + |
| 149 | + # Color code based on size difference magnitude |
| 150 | + if [[ "${diff}" -gt 10485760 ]]; then # > 10MB |
| 151 | + diff_color="${RED}" |
| 152 | + elif [[ "${diff}" -gt 1048576 ]]; then # > 1MB |
| 153 | + diff_color="${YELLOW}" |
| 154 | + else |
| 155 | + diff_color="${GREEN}" |
| 156 | + fi |
| 157 | + |
| 158 | + printf "%-40s ${BLUE}%-22s${NC} ${GREEN}%-22s${NC} ${diff_color}%-21s${NC}\n" \ |
| 159 | + "${name}" \ |
| 160 | + "$(numfmt --to=iec "${bsize}")" \ |
| 161 | + "$(numfmt --to=iec "${isize}")" \ |
| 162 | + "$(numfmt --to=iec "${diff}")" |
| 163 | + done < "${tmpfile}" |
| 164 | + |
| 165 | + echo -e "\n${PURPLE}Summary:${NC} Checked ${BOLD}${checked}${NC} common entries, skipped ${BOLD}${skipped}${NC} identical sizes." |
| 166 | + echo "=======================================================================================================" |
| 167 | + |
| 168 | + # List files only in build_dir |
| 169 | + echo -e "\n${BLUE}Only in Build IPA (${build_dir}):${NC}" |
| 170 | + echo "-------------------------------------------------------------------------------------------------------" |
| 171 | + comm -23 "${build_dir_list}" "${master_dir_list}" | while IFS= read -r name; do |
| 172 | + bsize=$(du -sb "${build_dir}/${name}" 2>/dev/null | cut -f1) |
| 173 | + echo -e "${bsize}\t${name}" |
| 174 | + done | sort -k1,1nr | while IFS=$'\t' read -r bsize name; do |
| 175 | + printf "${BLUE}%-40s %-22s${NC}\n" "${name}" "$(numfmt --to=iec "${bsize}")" |
| 176 | + done |
| 177 | + |
| 178 | + echo "=======================================================================================================" |
| 179 | + |
| 180 | + # List files only in master_dir |
| 181 | + echo -e "\n${GREEN}Only in Master IPA (${master_dir}):${NC}" |
| 182 | + echo "-------------------------------------------------------------------------------------------------------" |
| 183 | + comm -13 "${build_dir_list}" "${master_dir_list}" | while IFS= read -r name; do |
| 184 | + isize=$(du -sb "${master_dir}/${name}" 2>/dev/null | cut -f1) |
| 185 | + echo -e "${isize}\t${name}" |
| 186 | + done | sort -k1,1nr | while IFS=$'\t' read -r isize name; do |
| 187 | + printf "${GREEN}%-40s %-22s${NC}\n" "${name}" "$(numfmt --to=iec "${isize}")" |
| 188 | + done |
| 189 | + |
| 190 | + # Print some empty lines for clarity |
| 191 | + echo -e "\n\n\n" |
| 192 | +} |
| 193 | + |
| 194 | +# Print a rpm package list. The print omits packages that are identical in both |
| 195 | +#the built ipa and master. |
| 196 | +# Check if packages are a part of CentOS Stream in https://pkgs.org |
| 197 | +compare_rpm_packages() { |
| 198 | + build_dir="${1:-${BASE_DEBUG_DIR}/build-ipa-initramfs}" |
| 199 | + master_dir="${2:-${BASE_DEBUG_DIR}/master-ipa-initramfs}" |
| 200 | + build_rpm_list=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 201 | + master_rpm_list=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 202 | + build_rpm_list_name_bases=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 203 | + master_rpm_list_name_bases=$(mktemp -p "${BASE_HELPER_FILE_DIR}") |
| 204 | + |
| 205 | + sudo chroot "${build_dir}" rpm -qa | sort > "${build_rpm_list}" |
| 206 | + sudo chroot "${master_dir}" rpm -qa | sort > "${master_rpm_list}" |
| 207 | + |
| 208 | + common_count=$(comm -12 "${build_rpm_list}" "${master_rpm_list}" | wc -l) |
| 209 | + echo -e "\nNumber of common packages: ${common_count}\n" |
| 210 | + # Extract base package names and join for fuzzy matching |
| 211 | + awk -F'-[0-9]' '{print $1}' "${build_rpm_list}" | sort > "${build_rpm_list_name_bases}" |
| 212 | + awk -F'-[0-9]' '{print $1}' "${master_rpm_list}" | sort > "${master_rpm_list_name_bases}" |
| 213 | + |
| 214 | + printf "%-65s | %-65s\n" "In ${build_dir}" "In ${master_dir}" |
| 215 | + printf -- "----------------------------------------------------------------------------------------------------------------------------------\n" |
| 216 | + |
| 217 | + join -a1 -a2 -e '' -o 1.1,2.1 "${build_rpm_list_name_bases}" "${master_rpm_list_name_bases}" | \ |
| 218 | + while IFS=' ' read -r left right; do |
| 219 | + # Find the full package lines for each base name |
| 220 | + lfull=$(grep -m1 "^${left}-" "${build_rpm_list}" || echo "") |
| 221 | + rfull=$(grep -m1 "^${right}-" "${master_rpm_list}" || echo "") |
| 222 | + # Skip printing if both lines are non-empty and exactly the same |
| 223 | + if [[ -n "${lfull}" ]] && [[ "${lfull}" == "${rfull}" ]]; then |
| 224 | + continue |
| 225 | + fi |
| 226 | + printf "%-65s | %-65s\n" "${lfull}" "${rfull}" |
| 227 | + done |
| 228 | +} |
| 229 | + |
| 230 | +# After debugging and comparison operations, temporary directories can be cleaned up. |
| 231 | +# This function removes the build and master filesystems. If this operation is run |
| 232 | +# the set_up_debug_dirs needs to be run again to create the compareble filesystems. |
| 233 | +# Usage: |
| 234 | +# ./ipa_debug_tools.sh clean_up_debug_dirs |
| 235 | +# ----------------------------------------------------------------------------- |
| 236 | +clean_up_debug_dirs() { |
| 237 | + # Clean up debug directories |
| 238 | + sudo rm -rf "${BASE_DEBUG_DIR}" /tmp/dib ipa-file-injector.service 2> /dev/null |
| 239 | +} |
| 240 | + |
| 241 | +# Clean up all helper temp files. |
| 242 | +clean_up_debug_helpers() { |
| 243 | + sudo rm -rf "${BASE_HELPER_FILE_DIR}" 2> /dev/null |
| 244 | +} |
| 245 | + |
| 246 | +# Allow calling functions from command line |
| 247 | +# ----------------------------------------------------------------------------- |
| 248 | +# Display usage information |
| 249 | +usage() { |
| 250 | + cat << EOF |
| 251 | +Usage: ${0} <command> [options] |
| 252 | +
|
| 253 | +COMMANDS: |
| 254 | + set_up_debug_dirs [file] [url] |
| 255 | + Set up debug directories for comparing IPA initramfs |
| 256 | + |
| 257 | + Options: |
| 258 | + file Name of tar file to download (default: ipa-centos9-master.tar.gz) |
| 259 | + url URL to tarball archive (default: https://tarballs.opendev.org/openstack/ironic-python-agent/dib/) |
| 260 | +
|
| 261 | + compare_dir_sizes [relative_path] [base_build_dir] [base_master_dir] |
| 262 | + Compare sizes of files and directories |
| 263 | + |
| 264 | + Options: |
| 265 | + relative_path Path inside compared directories (default: "") |
| 266 | + base_build_dir First base directory (default: /tmp/debug-initramfs/build-ipa-initramfs) |
| 267 | + base_master_dir Second base directory (default: /tmp/debug-initramfs/master-ipa-initramfs) |
| 268 | +
|
| 269 | + compare_rpm_packages [build_dir] [master_dir] |
| 270 | + Compare RPM packages between two directories |
| 271 | + |
| 272 | + Options: |
| 273 | + build_dir Build directory (default: /tmp/debug-initramfs/build-ipa-initramfs) |
| 274 | + master_dir Master directory (default: /tmp/debug-initramfs/master-ipa-initramfs) |
| 275 | +
|
| 276 | + clean_up_debug_dirs |
| 277 | + Clean up all debug directories and temporary files |
| 278 | +
|
| 279 | + help, -h, --help |
| 280 | + Show this help message |
| 281 | +
|
| 282 | + EXAMPLE |
| 283 | + ./ipa_debug_tools.sh set_up_debug_dirs |
| 284 | + ./ipa_debug_tools.sh compare_dir_sizes |
| 285 | + ./ipa_debug_tools.sh compare_rpm_packages |
| 286 | + ./ipa_debug_tools.sh clean_up_debug_dirs |
| 287 | +
|
| 288 | +EOF |
| 289 | +} |
| 290 | + |
| 291 | +# Argument parsing |
| 292 | +main() { |
| 293 | + # Check if no arguments provided |
| 294 | + if [[ ${#} -eq 0 ]]; then |
| 295 | + echo "Error: Command missing" |
| 296 | + echo "" |
| 297 | + echo "Available commands: set_up_debug_dirs, compare_dir_sizes, compare_rpm_packages, clean_up_debug_dirs" |
| 298 | + echo "Use '${0} help' for detailed usage information" |
| 299 | + exit 1 |
| 300 | + fi |
| 301 | + |
| 302 | + local command="${1}" |
| 303 | + shift |
| 304 | + |
| 305 | + case "${command}" in |
| 306 | + help|-h|--help) |
| 307 | + usage |
| 308 | + exit 0 |
| 309 | + ;; |
| 310 | + set_up_debug_dirs|compare_dir_sizes|compare_rpm_packages|clean_up_debug_dirs) |
| 311 | + # Check if function exists and call it |
| 312 | + if declare -f "${command}" > /dev/null; then |
| 313 | + "${command}" "${@}" |
| 314 | + else |
| 315 | + echo "Error: Function '${command}' not implemented" |
| 316 | + exit 1 |
| 317 | + fi |
| 318 | + ;; |
| 319 | + *) |
| 320 | + echo "Error: Unknown command '${command}'" |
| 321 | + echo "" |
| 322 | + echo "Available commands: set_up_debug_dirs, compare_dir_sizes, compare_rpm_packages, clean_up_debug_dirs" |
| 323 | + echo "Use '${0} help' for detailed usage information" |
| 324 | + exit 1 |
| 325 | + ;; |
| 326 | + esac |
| 327 | +} |
| 328 | + |
| 329 | +main "${@}" |
0 commit comments