Skip to content

Commit

Permalink
fix: apt cache performance (#104)
Browse files Browse the repository at this point in the history
* fix: apt cache performance

Use a single call to apt-cache to reduce the time needed to lookup
package versions.

Also:
* Added millisecond details to log timing so slow operations can be more
  easily identified.
* Perform apt update before determining package versions.

Fixes #103

* chore: descriptive variable names and use log_err

Added the review feedback, updating variable names to be more
descriptive and using log_err where appropriate.
  • Loading branch information
stevenh committed Oct 11, 2023
1 parent 1850ee5 commit 641f947
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 54 deletions.
38 changes: 16 additions & 22 deletions install_and_cache_pkgs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,6 @@ cache_dir="${1}"
# List of the packages to use.
input_packages="${@:3}"

# Trim commas, excess spaces, sort, and version syntax.
#
# NOTE: Unless specified, all APT package listings of name and version use
# colon delimited and not equals delimited syntax (i.e. <name>[:=]<ver>).
packages="$(get_normalized_package_list "${input_packages}")"

package_count=$(wc -w <<< "${packages}")
log "Clean installing and caching ${package_count} package(s)."

log_empty_line

manifest_main=""
log "Package list:"
for package in ${packages}; do
read package_name package_ver < <(get_package_name_ver "${package}")
manifest_main="${manifest_main}${package_name}=${package_ver},"
log "- ${package_name} (${package_ver})"
done
write_manifest "main" "${manifest_main}" "${cache_dir}/manifest_main.log"

log_empty_line

if ! apt-fast --version > /dev/null 2>&1; then
log "Installing apt-fast for optimized installs..."
# Install apt-fast for optimized installs.
Expand All @@ -59,6 +37,22 @@ fi

log_empty_line

packages="$(get_normalized_package_list "${input_packages}")"
package_count=$(wc -w <<< "${packages}")
log "Clean installing and caching ${package_count} package(s)."

log_empty_line

manifest_main=""
log "Package list:"
for package in ${packages}; do
manifest_main="${manifest_main}${package},"
log "- ${package}"
done
write_manifest "main" "${manifest_main}" "${cache_dir}/manifest_main.log"

log_empty_line

# Strictly contains the requested packages.
manifest_main=""
# Contains all packages including dependencies.
Expand Down
73 changes: 61 additions & 12 deletions lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function get_installed_packages {
###############################################################################
# Splits a fully action syntax APT package into the name and version.
# Arguments:
# The action syntax colon delimited package pair or just the package name.
# The action syntax equals delimited package pair or just the package name.
# Returns:
# The package name and version pair.
###############################################################################
Expand All @@ -81,7 +81,9 @@ function get_package_name_ver {
IFS="${ORIG_IFS}"
# If version not found in the fully qualified package value.
if test -z "${ver}"; then
ver="$(grep "Version:" <<< "$(apt-cache show ${name})" | awk '{print $2}')"
# This is a fallback and should not be used any more as its slow.
log_err "Unexpected version resolution for package '${name}'"
ver="$(apt-cache show ${name} | grep '^Version:' | awk '{print $2}')"
fi
echo "${name}" "${ver}"
}
Expand All @@ -91,16 +93,63 @@ function get_package_name_ver {
# Arguments:
# The comma and/or space delimited list of packages.
# Returns:
# Sorted list of space delimited packages.
# Sorted list of space delimited package name=version pairs.
###############################################################################
function get_normalized_package_list {
# Remove commas, and block scalar folded backslashes.
local stripped=$(echo "${1}" | sed 's/[,\]/ /g')
# Remove extraneous spaces at the middle, beginning, and end.
local trimmed="$(\
echo "${stripped}" \
| sed 's/\s\+/ /g; s/^\s\+//g; s/\s\+$//g')"
echo ${trimmed} | tr ' ' '\n' | sort | tr '\n' ' '
# Remove commas, and block scalar folded backslashes,
# extraneous spaces at the middle, beginning and end
# then sort.
packages=$(echo "${1}" \
| sed 's/[,\]/ /g; s/\s\+/ /g; s/^\s\+//g; s/\s\+$//g' \
| sort -t' ')

# Validate package names and get versions.
log_err "resolving package versions..."
data=$(apt-cache --quiet=0 --no-all-versions show ${packages} 2>&1 | \
grep -E '^(Package|Version|N):')
log_err "resolved"

local ORIG_IFS="${IFS}"
IFS=$'\n'
declare -A missing
local package_versions=''
local package='' separator=''
for key_value in ${data}; do
local key="${key_value%%: *}"
local value="${key_value##*: }"

case $key in
Package)
package=$value
;;
Version)
package_versions="${package_versions}${separator}"${package}=${value}""
separator=' '
;;
N)
# Warning messages.
case $value in
'Unable to locate package '*)
package="${value#'Unable to locate package '}"
# Avoid duplicate messages.
if [ -z "${missing[$package]}" ]; then
package="${value#'Unable to locate package '}"
log_err "Package '${package}' not found."
missing[$package]=1
fi
;;
esac
;;
esac
done
IFS="${ORIG_IFS}"

if [ ${#missing[@]} -gt 0 ]; then
echo "aborted"
exit 5
fi

echo "${package_versions}"
}

###############################################################################
Expand All @@ -120,8 +169,8 @@ function get_tar_relpath {
fi
}

function log { echo "$(date +%H:%M:%S)" "${@}"; }
function log_err { >&2 echo "$(date +%H:%M:%S)" "${@}"; }
function log { echo "$(date +%T.%3N)" "${@}"; }
function log_err { >&2 echo "$(date +%T.%3N)" "${@}"; }

function log_empty_line { echo ""; }

Expand Down
21 changes: 1 addition & 20 deletions pre_cache_action.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,16 @@ log "done"

log_empty_line

versioned_packages=""
log "Verifying packages..."
for package in ${packages}; do
if test ! "$(apt-cache show ${package})"; then
echo "aborted"
log "Package '${package}' not found." >&2
exit 5
fi
read package_name package_ver < <(get_package_name_ver "${package}")
versioned_packages=""${versioned_packages}" "${package_name}"="${package_ver}""
done
log "done"

log_empty_line

# Abort on any failure at this point.
set -e

log "Creating cache key..."

# TODO Can we prove this will happen again?
normalized_versioned_packages="$(get_normalized_package_list "${versioned_packages}")"
log "- Normalized package list is '${normalized_versioned_packages}'."

# Forces an update in cases where an accidental breaking change was introduced
# and a global cache reset is required.
force_update_inc="1"

value="${normalized_versioned_packages} @ ${version} ${force_update_inc}"
value="${packages} @ ${version} ${force_update_inc}"
log "- Value to hash is '${value}'."

key="$(echo "${value}" | md5sum | cut -f1 -d' ')"
Expand Down

0 comments on commit 641f947

Please sign in to comment.