at 25.11-pre 9.2 kB view raw
1{ 2 lib, 3 runCommand, 4 writeShellScript, 5 writeText, 6 linkFarm, 7 time, 8 procps, 9 nixVersions, 10 jq, 11 sta, 12 python3, 13}: 14 15let 16 nixpkgs = 17 with lib.fileset; 18 toSource { 19 root = ../..; 20 fileset = unions ( 21 map (lib.path.append ../..) [ 22 "default.nix" 23 "doc" 24 "lib" 25 "maintainers" 26 "nixos" 27 "pkgs" 28 ".version" 29 "ci/supportedSystems.json" 30 ] 31 ); 32 }; 33 34 nix = nixVersions.nix_2_24; 35 36 supportedSystems = builtins.fromJSON (builtins.readFile ../supportedSystems.json); 37 38 attrpathsSuperset = 39 runCommand "attrpaths-superset.json" 40 { 41 src = nixpkgs; 42 nativeBuildInputs = [ 43 nix 44 time 45 ]; 46 } 47 '' 48 export NIX_STATE_DIR=$(mktemp -d) 49 mkdir $out 50 export GC_INITIAL_HEAP_SIZE=4g 51 command time -f "Attribute eval done [%MKB max resident, %Es elapsed] %C" \ 52 nix-instantiate --eval --strict --json --show-trace \ 53 "$src/pkgs/top-level/release-attrpaths-superset.nix" \ 54 -A paths \ 55 -I "$src" \ 56 --option restrict-eval true \ 57 --option allow-import-from-derivation false \ 58 --arg enableWarnings false > $out/paths.json 59 ''; 60 61 singleSystem = 62 { 63 # The system to evaluate. 64 # Note that this is intentionally not called `system`, 65 # because `--argstr system` would only be passed to the ci/default.nix file! 66 evalSystem, 67 # The path to the `paths.json` file from `attrpathsSuperset` 68 attrpathFile ? "${attrpathsSuperset}/paths.json", 69 # The number of attributes per chunk, see ./README.md for more info. 70 chunkSize, 71 checkMeta ? true, 72 includeBroken ? true, 73 # Whether to just evaluate a single chunk for quick testing 74 quickTest ? false, 75 }: 76 let 77 singleChunk = writeShellScript "single-chunk" '' 78 set -euo pipefail 79 chunkSize=$1 80 myChunk=$2 81 system=$3 82 outputDir=$4 83 84 export NIX_SHOW_STATS=1 85 export NIX_SHOW_STATS_PATH="$outputDir/stats/$myChunk" 86 echo "Chunk $myChunk on $system start" 87 set +e 88 command time -o "$outputDir/timestats/$myChunk" \ 89 -f "Chunk $myChunk on $system done [%MKB max resident, %Es elapsed] %C" \ 90 nix-env -f "${nixpkgs}/pkgs/top-level/release-attrpaths-parallel.nix" \ 91 --eval-system "$system" \ 92 --option restrict-eval true \ 93 --option allow-import-from-derivation false \ 94 --query --available \ 95 --no-name --attr-path --out-path \ 96 --show-trace \ 97 --arg chunkSize "$chunkSize" \ 98 --arg myChunk "$myChunk" \ 99 --arg attrpathFile "${attrpathFile}" \ 100 --arg systems "[ \"$system\" ]" \ 101 --arg checkMeta ${lib.boolToString checkMeta} \ 102 --arg includeBroken ${lib.boolToString includeBroken} \ 103 -I ${nixpkgs} \ 104 -I ${attrpathFile} \ 105 > "$outputDir/result/$myChunk" \ 106 2> "$outputDir/stderr/$myChunk" 107 exitCode=$? 108 set -e 109 cat "$outputDir/stderr/$myChunk" 110 cat "$outputDir/timestats/$myChunk" 111 if (( exitCode != 0 )); then 112 echo "Evaluation failed with exit code $exitCode" 113 # This immediately halts all xargs processes 114 kill $PPID 115 elif [[ -s "$outputDir/stderr/$myChunk" ]]; then 116 echo "Nixpkgs on $system evaluated with warnings, aborting" 117 kill $PPID 118 fi 119 ''; 120 in 121 runCommand "nixpkgs-eval-${evalSystem}" 122 { 123 nativeBuildInputs = [ 124 nix 125 time 126 procps 127 jq 128 ]; 129 env = { 130 inherit evalSystem chunkSize; 131 }; 132 } 133 '' 134 export NIX_STATE_DIR=$(mktemp -d) 135 nix-store --init 136 137 echo "System: $evalSystem" 138 cores=$NIX_BUILD_CORES 139 echo "Cores: $cores" 140 attrCount=$(jq length "${attrpathFile}") 141 echo "Attribute count: $attrCount" 142 echo "Chunk size: $chunkSize" 143 # Same as `attrCount / chunkSize` but rounded up 144 chunkCount=$(( (attrCount - 1) / chunkSize + 1 )) 145 echo "Chunk count: $chunkCount" 146 147 mkdir $out 148 149 # Record and print stats on free memory and swap in the background 150 ( 151 while true; do 152 availMemory=$(free -b | grep Mem | awk '{print $7}') 153 freeSwap=$(free -b | grep Swap | awk '{print $4}') 154 echo "Available memory: $(( availMemory / 1024 / 1024 )) MiB, free swap: $(( freeSwap / 1024 / 1024 )) MiB" 155 156 if [[ ! -f "$out/min-avail-memory" ]] || (( availMemory < $(<$out/min-avail-memory) )); then 157 echo "$availMemory" > $out/min-avail-memory 158 fi 159 if [[ ! -f $out/min-free-swap ]] || (( availMemory < $(<$out/min-free-swap) )); then 160 echo "$freeSwap" > $out/min-free-swap 161 fi 162 sleep 4 163 done 164 ) & 165 166 seq_end=$(( chunkCount - 1 )) 167 168 ${lib.optionalString quickTest '' 169 seq_end=0 170 ''} 171 172 chunkOutputDir=$(mktemp -d) 173 mkdir "$chunkOutputDir"/{result,stats,timestats,stderr} 174 175 seq -w 0 "$seq_end" | 176 command time -f "%e" -o "$out/total-time" \ 177 xargs -I{} -P"$cores" \ 178 ${singleChunk} "$chunkSize" {} "$evalSystem" "$chunkOutputDir" 179 180 cp -r "$chunkOutputDir"/stats $out/stats-by-chunk 181 182 if (( chunkSize * chunkCount != attrCount )); then 183 # A final incomplete chunk would mess up the stats, don't include it 184 rm "$chunkOutputDir"/stats/"$seq_end" 185 fi 186 187 # Make sure the glob doesn't break when there's no files 188 shopt -s nullglob 189 cat "$chunkOutputDir"/result/* > $out/paths 190 cat "$chunkOutputDir"/stats/* > $out/stats.jsonstream 191 ''; 192 193 combine = 194 { 195 resultsDir, 196 }: 197 runCommand "combined-result" 198 { 199 nativeBuildInputs = [ 200 jq 201 sta 202 ]; 203 } 204 '' 205 mkdir -p $out 206 207 # Transform output paths to JSON 208 cat ${resultsDir}/*/paths | 209 jq --sort-keys --raw-input --slurp ' 210 split("\n") | 211 map(select(. != "") | split(" ") | map(select(. != ""))) | 212 map( 213 { 214 key: .[0], 215 value: .[1] | split(";") | map(split("=") | 216 if length == 1 then 217 { key: "out", value: .[0] } 218 else 219 { key: .[0], value: .[1] } 220 end) | from_entries} 221 ) | from_entries 222 ' > $out/outpaths.json 223 224 # Computes min, mean, error, etc. for a list of values and outputs a JSON from that 225 statistics() { 226 local stat=$1 227 sta --transpose | 228 jq --raw-input --argjson stat "$stat" -n ' 229 [ 230 inputs | 231 split("\t") | 232 { key: .[0], value: (.[1] | fromjson) } 233 ] | 234 from_entries | 235 { 236 key: ($stat | join(".")), 237 value: . 238 }' 239 } 240 241 # Gets all available number stats (without .sizes because those are constant and not interesting) 242 readarray -t stats < <(jq -cs '.[0] | del(.sizes) | paths(type == "number")' ${resultsDir}/*/stats.jsonstream) 243 244 # Combines the statistics from all evaluations 245 { 246 echo "{ \"key\": \"minAvailMemory\", \"value\": $(cat ${resultsDir}/*/min-avail-memory | sta --brief --min) }" 247 echo "{ \"key\": \"minFreeSwap\", \"value\": $(cat ${resultsDir}/*/min-free-swap | sta --brief --min) }" 248 cat ${resultsDir}/*/total-time | statistics '["totalTime"]' 249 for stat in "''${stats[@]}"; do 250 cat ${resultsDir}/*/stats.jsonstream | 251 jq --argjson stat "$stat" 'getpath($stat)' | 252 statistics "$stat" 253 done 254 } | 255 jq -s from_entries > $out/stats.json 256 257 mkdir -p $out/stats 258 259 for d in ${resultsDir}/*; do 260 cp -r "$d"/stats-by-chunk $out/stats/$(basename "$d") 261 done 262 ''; 263 264 compare = import ./compare { 265 inherit 266 lib 267 jq 268 runCommand 269 writeText 270 supportedSystems 271 python3 272 ; 273 }; 274 275 full = 276 { 277 # Whether to evaluate on a specific set of systems, by default all are evaluated 278 evalSystems ? if quickTest then [ "x86_64-linux" ] else supportedSystems, 279 # The number of attributes per chunk, see ./README.md for more info. 280 chunkSize, 281 quickTest ? false, 282 }: 283 let 284 results = linkFarm "results" ( 285 map (evalSystem: { 286 name = evalSystem; 287 path = singleSystem { 288 inherit quickTest evalSystem chunkSize; 289 }; 290 }) evalSystems 291 ); 292 in 293 combine { 294 resultsDir = results; 295 }; 296 297in 298{ 299 inherit 300 attrpathsSuperset 301 singleSystem 302 combine 303 compare 304 # The above three are used by separate VMs in a GitHub workflow, 305 # while the below is intended for testing on a single local machine 306 full 307 ; 308}