#!/usr/bin/env bash12# SPDX-FileCopyrightText: Copyright The Lima Authors3# SPDX-License-Identifier: Apache-2.045# This script calculates the expected content size, actual cached size, and cache-keys used in caching method before and after6# implementation in https://github.com/lima-vm/lima/pull/25087#8# Answer to the question in https://github.com/lima-vm/lima/pull/2508#discussion_r1699798651910scriptdir=$(dirname "${BASH_SOURCE[0]}")11# shellcheck source=./common.inc.sh12. "${scriptdir}/cache-common-inc.sh"1314# usage: [DEBUG=1] ./hack/calculate-cache.sh15# DEBUG=1 will save the collected information in .calculate-cache-collected-info-{before,after}.yaml16#17# This script does:18# 1. extracts `runs_on` and `template` from workflow file (.github/workflows/test.yml)19# 2. check each template for image, kernel, initrd, and nerdctl20# 3. detect size of image, kernel, initrd, and nerdctl (responses from remote are cached for faster iteration)21# save the response in .check_location-response-cache.yaml22# 4. print content size, actual cache size (if available), by cache key23#24# The major differences for reducing cache usage are as follows:25# - it is now cached `~/.cache/lima/download/by-url-sha256/$sha256` instead of caching `~/.cache/lima/download`26# - the cache keys are now based on the image, kernel, initrd, and nerdctl digest instead of the template file's hash27# - enables the use of cache regardless of the operating system used to execute CI.28#29# The script requires the following commands:30# - gh: GitHub CLI.31# Using to get the cache information32# - jq: Command-line JSON processor33# Parse the workflow file and print runs-on and template.34# Parse output from gh cache list35# Calculate the expected content size, actual cached size, and cache-keys used.36# - limactl: lima CLI.37# Using to validate the template file for getting nerdctl location and digest.38# - sha256sum: Print or check SHA256 (256-bit) checksums39# - xxd: make a hexdump or do the reverse.40# Using to simulate the 'hashFile()' function in the workflow.41# - yq: Command-line YAML processor.42# Parse the template file for image and nerdctl location, digest, and size.43# Parse the cache response file for the cache.44# Convert the collected information to JSON.4546set -u -o pipefail4748required_commands=(gh jq limactl sha256sum xxd yq)49for cmd in "${required_commands[@]}"; do50if ! command -v "${cmd}" &>/dev/null; then51echo "${cmd} is required. Please install it" >&252exit 153fi54done5556# current workflow uses x86_64 only57arch=x86_645859LIMA_HOME=$(mktemp -d)60export LIMA_HOME6162# parse the workflow file and print runs-on and template63# e.g.64# ```console65# $ print_runs_on_template_from_workflow .github/workflows/test.yml66# macos-12 templates/default.yaml67# ubuntu-24.04 templates/alpine.yaml68# ubuntu-24.04 templates/debian.yaml69# ubuntu-24.04 templates/fedora.yaml70# ubuntu-24.04 templates/archlinux.yaml71# ubuntu-24.04 templates/opensuse.yaml72# ubuntu-24.04 templates/experimental/net-user-v2.yaml73# ubuntu-24.04 templates/experimental/9p.yaml74# ubuntu-24.04 templates/docker.yaml75# ubuntu-24.04 templates/../hack/test-templates/alpine-iso-9p-writable.yaml76# ubuntu-24.04 templates/../hack/test-templates/test-misc.yaml77# macos-12 templates/vmnet.yaml78# macos-12 https://raw.githubusercontent.com/lima-vm/lima/v0.15.1/examples/ubuntu-lts.yaml79# macos-13 templates/experimental/vz.yaml80# macos-13 templates/fedora.yaml81# ```82function print_runs_on_template_from_workflow() {83yq -o=j "$1" | jq -r '84"./.github/actions/setup_cache_for_template" as $action |85"\\$\\{\\{\\s*(?<path>\\S*)\\s*\\}\\}" as $pattern |86.jobs | map_values(select(.steps)|87."runs-on" as $runs_on |88{89template: .steps | map_values(select(.uses == $action)) | first |.with.template,90matrix: .strategy.matrix91} | select(.template) |92. + { path: .template | (if test($pattern) then sub(".*\($pattern).*";"\(.path)")|split(".") else null end) } |93(94.template as $template|95if .path then96getpath(.path)|map(. as $item|$template|sub($pattern;$item))97else98[$template]99end100) | map("\($runs_on)\t\(.)")101102) | flatten |.[]103'104}105106# returns the OS name from the runner equivalent to the expression `${{ runner.os }}` in the workflow107# e.g.108# ```console109# $ runner_os_from_runner "macos-12"110# macOS111# $ runner_os_from_runner "ubuntu-24.04"112# Linux113# ```114function runner_os_from_runner() {115# shellcheck disable=SC2249116case "$1" in117macos*)118echo macOS119;;120ubuntu*)121echo Linux122;;123esac124}125126# format first column to MiB127# e.g.128# ```console129# $ echo 585498624 | size_to_mib130# 558.38 MiB131# ```132function size_to_mib() {133awk '134function mib(size) { return sprintf("%7.2f MiB", size / 1024 / 1024) }135int($1)>0{ $1=" "mib($1) }136int($2)>0{ $2=mib($2) }137int($2)==0 && NF>1{ $2="<<missing>>" }138{ print }139'140}141142# actual_cache_sizes=$(gh cache list --json key,createdAt,sizeInBytes|jq '[.[]|{"key":.key,"value":.sizeInBytes}]|from_entries')143# e.g.144# ```console145# $ echo "${actual_cache_sizes}"146# {147# "Linux-1c3b2791d52735d916dc44767c745c2319eb7cae74af71bbf45ddb268f42fc1d": 810758533,148# "Linux-231c66957fc2cdb18ea10e63f60770049026e29051ecd6598fc390b60d6a4fa6": 633036717,149# "Linux-3b906d46fa532e3bc348c35fc8e7ede6c69f0b27032046ee2cbb56d4022d1146": 574242367,150# "Linux-69a547b760dbf1650007ed541408474237bc611704077214adcac292de556444": 70310855,151# "Linux-7782f8b4ff8cd378377eb79f8d61c9559b94bbd0c11d19eb380ee7bda19af04e": 494141177,152# "Linux-8812aedfe81b4456d421645928b493b1f2f88aff04b7f3171207492fd44cd189": 812730766,153# "Linux-caa7d8af214d55ad8902e82d5918e61573f3d6795d2b5ad9a35305e26fa0e6a9": 754723892,154# "Linux-colima-v0.6.5": 226350335,155# "Linux-de83bce0608d787e3c68c7a31c5fab2b6d054320fd7bf633a031845e2ee03414": 810691197,156# "Linux-eb88a19dfcf2fb98278e7c7e941c143737c6d7cd8950a88f58e04b4ee7cef1bc": 570625794,157# "Linux-f88f0b3b678ff6432386a42bdd27661133c84a36ad29f393da407c871b0143eb": 68490954,158# "golangci-lint.cache-Linux-2850-74615231540133417fd618c72e37be92c5d3b3ad": 2434144,159# "macOS-231c66957fc2cdb18ea10e63f60770049026e29051ecd6598fc390b60d6a4fa6": 633020464,160# "macOS-49aa50a4872ded07ebf657c0eaf9e44ecc0c174d033a97c537ecd270f35b462f": 813179462,161# "macOS-8f37f663956af5f743f0f99ab973729b6a02f200ebfac7a3a036eff296550732": 810756770,162# "macOS-ef5509b5d4495c8c3590442ee912ad1c9a33f872dc4a29421c524fc1e2103b59": 813179476,163# "macOS-upgrade-v0.15.1": 1157814690,164# "setup-go-Linux-ubuntu20-go-1.23.0-02756877dbcc9669bb904e42e894c63aa9801138db94426a90a2d554f2705c52": 1015518352,165# "setup-go-Linux-ubuntu20-go-1.23.0-6bce2eefc6111ace836de8bb322432c072805737d5f3c5a3d47d2207a05f50df": 936433302,166# "setup-go-Linux-ubuntu24-go-1.22.6-02756877dbcc9669bb904e42e894c63aa9801138db94426a90a2d554f2705c52": 1090001859,167# "setup-go-Linux-ubuntu24-go-1.23.0-02756877dbcc9669bb904e42e894c63aa9801138db94426a90a2d554f2705c52": 526146768,168# "setup-go-Windows-go-1.23.0-02756877dbcc9669bb904e42e894c63aa9801138db94426a90a2d554f2705c52": 1155374040,169# "setup-go-Windows-go-1.23.0-6bce2eefc6111ace836de8bb322432c072805737d5f3c5a3d47d2207a05f50df": 1056433137,170# "setup-go-macOS-go-1.23.0-02756877dbcc9669bb904e42e894c63aa9801138db94426a90a2d554f2705c52": 1060919942,171# "setup-go-macOS-go-1.23.0-6bce2eefc6111ace836de8bb322432c072805737d5f3c5a3d47d2207a05f50df": 982139209172# }173actual_cache_sizes=$(174gh cache list --json key,createdAt,sizeInBytes \175--jq 'sort_by(.createdAt)|reverse|unique_by(.key)|sort_by(.key)|map({"key":.key,"value":.sizeInBytes})|from_entries'176)177178workflows=(179.github/workflows/test.yml180)181182# shellcheck disable=SC2016183echo "=> compare expected content size, actual cached size, and cache-keys used before and after the change in https://github.com/lima-vm/lima/pull/2508"184# iterate over before and after185for cache_method in before after; do186echo "==> ${cache_method}"187echo "content-size actual-size cache-key"188output_yaml=$(189for workflow in "${workflows[@]}"; do190print_runs_on_template_from_workflow "${workflow}"191done | while IFS=$'\t' read -r runner template; do192template=$(download_template_if_needed "${template}") || continue193arch=$(detect_arch "${template}" "${arch}") || continue194index=$(print_image_locations_for_arch_from_template "${template}" "${arch}" | print_valid_image_index) || continue195image_kernel_initrd_info=$(print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index "${template}" "${index}" "${arch}") || continue196# shellcheck disable=SC2034 # shellcheck does not detect dynamic variables usage197read -r image_location image_digest kernel_location kernel_digest initrd_location initrd_digest <<<"${image_kernel_initrd_info}"198containerd_info=$(print_containerd_config_for_arch_from_template "${template}" "${@:2}") || continue199# shellcheck disable=SC2034 # shellcheck does not detect dynamic variables usage200read -r _containerd_enabled containerd_location containerd_digest <<<"${containerd_info}"201202if [[ ${cache_method} != after ]]; then203key=$(runner_os_from_runner "${runner}" || true)-$(hash_file "${template}")204else205key=$(cache_key_from_prefix_location_and_digest image "${image_location}" "${image_digest}")206fi207size=$(size_from_location "${image_location}")208for prefix in containerd kernel initrd; do209location="${prefix}_location"210digest="${prefix}_digest"211[[ ${!location} != null ]] || continue212if [[ ${cache_method} != after ]]; then213# previous caching method packages all files in download to a single cache key214size=$((size + $(size_from_location "${!location}")))215else216# new caching method caches each file separately217key_for_prefix=$(cache_key_from_prefix_location_and_digest "${prefix}" "${!location}" "${!digest}")218size_for_prefix=$(size_from_location "${!location}")219printf -- "- key: %s\n template: %s\n location: %s\n digest: %s\n size: %s\n" \220"${key_for_prefix}" "${template}" "${!location}" "${!digest}" "${size_for_prefix}"221fi222done223printf -- "- key: %s\n template: %s\n location: %s\n digest: %s\n size: %s\n" \224"${key}" "${template}" "${image_location}" "${image_digest}" "${size}"225done226)227output_json=$(yq -o=j . <<<"${output_yaml}")228229# print size key230jq --argjson actual_size "${actual_cache_sizes}" -r 'unique_by(.key)|sort_by(.key)|.[]|[.size, $actual_size[.key] // 0, .key]|@tsv' <<<"${output_json}" | size_to_mib231# total232echo "------------"233jq '[unique_by(.key)|.[]|.size]|add' <<<"${output_json}" | size_to_mib234# save the collected information as yaml if DEBUG is set235if [[ -n ${DEBUG:+1} ]]; then236cat <<<"${output_yaml}" >".calculate-cache-collected-info-${cache_method}.yaml"237echo "Saved the collected information in .calculate-cache-collected-info-${cache_method}.yaml"238fi239echo ""240done241242243