11#! /bin/bash
22
3- usage () { echo " Usage: $0 [-a <x86_64|arm64> -o <file01.bpf.o> -o <file02.bpf.o>]" 1>&2 ; exit 1; }
3+ usage () {
4+ echo " Usage: $0 [-a <x86_64|arm64> -o <file01.bpf.o> -o <file02.bpf.o>] [-j <num_jobs>]" 1>&2
5+ exit 1
6+ }
7+
8+ # Set default jobs to 1
9+ j=1
10+
11+ # Get number of CPUs for validation when user sets -j
12+ if command -v nproc > /dev/null 2>&1 ; then
13+ max_jobs=$( nproc)
14+ elif [ -f /proc/cpuinfo ]; then
15+ max_jobs=$( grep -c ^processor /proc/cpuinfo)
16+ else
17+ max_jobs=1
18+ fi
19+ # Fallback to 1 if detection failed or result is empty/non-numeric
20+ if ! [ " ${max_jobs} " -ge 1 ] 2> /dev/null; then
21+ max_jobs=1
22+ fi
423
5- on=0
24+ o=()
625
7- while getopts " :a:o:" opt; do
26+ while getopts " :a:o:j: " opt; do
827 case " ${opt} " in
928 a)
10- a=${OPTARG}
29+ a=" ${OPTARG} "
1130 [[ " ${a} " != " x86_64" && " ${a} " != " arm64" ]] && usage
12- ;;
31+ ;;
1332 o)
14- [[ ! -f ${OPTARG} ]] && { echo " error: could not find bpf object: ${OPTARG} " ; usage; }
33+ [[ ! -f " ${OPTARG} " ]] && {
34+ echo " error: could not find bpf object: ${OPTARG} "
35+ usage
36+ }
1537 o+=(" ${OPTARG} " )
16- ;;
38+ ;;
39+ j)
40+ j=" ${OPTARG} "
41+ # Validate it's a positive integer
42+ if ! [ " ${j} " -ge 1 ] 2> /dev/null; then
43+ echo " error: -j must be a positive integer (got: ${j} )"
44+ usage
45+ fi
46+ # Cap at CPU count if exceeded
47+ if [ " ${j} " -gt " ${max_jobs} " ]; then
48+ echo " warning: -j ${j} exceeds CPU count (${max_jobs} ), using ${max_jobs} "
49+ j=" ${max_jobs} "
50+ fi
51+ ;;
1752 * )
1853 usage
19- ;;
54+ ;;
2055 esac
2156done
22- shift $(( OPTIND- 1 ))
57+ shift $(( OPTIND - 1 ))
2358
24- if [ -z " ${a} " ] || [ -z " ${o} " ]; then
59+ if [ -z " ${a} " ] || [ ${ # o[@]} -eq 0 ]; then
2560 usage
2661fi
2762
@@ -30,19 +65,19 @@ for ofile in "${o[@]}"; do
3065 obj_cmdline+=" ${ofile} "
3166done
3267
33- basedir=$( dirname ${0} ) /..
68+ basedir=$( dirname " ${0} " ) /..
3469if [ " ${basedir} " == " ." ]; then
35- basedir=$( pwd) /..
70+ basedir=" $( pwd) /.."
3671fi
3772
38- if [ ! -d ${basedir} /archive ]; then
73+ if [ ! -d " ${basedir} /archive" ]; then
3974 echo " error: could not find archive directory"
4075 exit 1
4176fi
4277
43- cd ${basedir}
78+ cd " ${basedir} " || exit 1
4479
45- btfgen=$( which bpftool)
80+ btfgen=" $( which bpftool) "
4681if [ -z " ${btfgen} " ]; then
4782 btfgen=/usr/sbin/bpftool
4883fi
@@ -52,10 +87,30 @@ if [ ! -x "${btfgen}" ]; then
5287 exit 1
5388fi
5489
55- function ctrlc ()
56- {
90+ # Track background jobs for cleanup
91+ declare -a background_pids=()
92+
93+ function ctrlc() {
5794 echo " Exiting due to ctrl-c..."
58- rm ${basedir} /* .btf
95+
96+ # Kill all background jobs
97+ for pid in " ${background_pids[@]} " ; do
98+ if kill -0 " ${pid} " 2> /dev/null; then
99+ kill " ${pid} " 2> /dev/null
100+ fi
101+ done
102+
103+ # Wait a bit for graceful shutdown
104+ sleep 1
105+
106+ # Force kill if still running
107+ for pid in " ${background_pids[@]} " ; do
108+ if kill -0 " ${pid} " 2> /dev/null; then
109+ kill -9 " ${pid} " 2> /dev/null
110+ fi
111+ done
112+
113+ rm -f " ${basedir} " /* .btf
59114
60115 exit 2
61116}
@@ -66,21 +121,152 @@ trap ctrlc SIGTERM
66121# clean custom-archive directory
67122find ./custom-archive -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} \;
68123
69- for dir in $( find ./archive/ -iregex " .*${a} .*" -type d | sed ' s:\.\/archive\/::g' | sort -u) ; do
70- # uncompress and process each existing input BTF .tar.xz file
71- for file in $( find ./archive/${dir} -name * .tar.xz) ; do
72- dir=$( dirname $file )
73- base=$( basename $file )
74- extracted=$( tar xvfJ $dir /$base ) ; ret=$?
124+ # Function to process a single BTF file
125+ process_btf_file () {
126+ local file=" $1 "
127+ local btfgen=" $2 "
128+ local obj_cmdline=" $3 "
129+
130+ local dir
131+ local extracted
132+ local temp_dir
133+ local original_dir
134+
135+ dir=" $( dirname " ${file} " ) "
136+
137+ # Create a temporary directory for this job to avoid conflicts
138+ temp_dir=" $( mktemp -d) "
139+ original_dir=" $( pwd) "
140+
141+ # Extract in temp directory
142+ cd " ${temp_dir} " || return 1
143+ extracted=" $( tar xvfJ " ${original_dir} /${file} " 2> /dev/null) "
144+ local ret=$?
145+
146+ if [[ ${ret} -eq 0 && -f " ${extracted} " ]]; then
147+ cd " ${original_dir} " || return 1
148+
149+ # Prepare output directory
150+ dir=${dir/ \.\/ archive\/ / }
151+ local out_dir=" ./custom-archive/${dir} "
152+ mkdir -p " ${out_dir} "
153+
154+ # Move extracted file to working directory and process
155+ mv " ${temp_dir} /${extracted} " " ./${extracted} "
156+
157+ # Generate minimized BTF file
158+ # shellcheck disable=SC2086
159+ " ${btfgen} " gen min_core_btf " ${extracted} " " ${out_dir} /${extracted} " ${obj_cmdline}
160+ local btfgen_ret=$?
75161
76- dir=${dir/ \.\/ archive\/ }
77- out_dir=" ./custom-archive/${dir} "
78- [[ ! -d ${out_dir} ]] && mkdir -p ${out_dir}
162+ # Cleanup
163+ rm -f " ./${extracted} "
79164
80- echo " Processing ${extracted} ..."
165+ if [[ ${btfgen_ret} -eq 0 ]]; then
166+ printf " [SUCCESS] %s\n" " ${extracted} "
167+ # Cleanup temp directory
168+ rm -rf " ${temp_dir} "
169+ return 0
170+ else
171+ printf " [FAIL] %s\n" " ${extracted} "
172+ # Cleanup temp directory
173+ rm -rf " ${temp_dir} "
174+ return 1
175+ fi
176+ else
177+ cd " ${original_dir} " || return 1
178+ printf " [FAIL] %s (extraction failed)\n" " $( basename " ${file} " ) "
179+ # Cleanup temp directory
180+ rm -rf " ${temp_dir} "
181+ return 1
182+ fi
183+ }
184+
185+ # Export the function so it can be used by background processes
186+ export -f process_btf_file
187+
188+ echo " Using ${j} parallel jobs for BTF processing..."
189+
190+ # Ensure output is line-buffered for better real-time display
191+ stty -icanon min 1 time 0 2> /dev/null || true
192+
193+ # Initialize job control variables
194+ job_count=0
195+ failed_jobs=0
196+ completed_jobs=0
197+ start_time=" $( date +%s) "
198+
199+ # Collect all BTF files to process
200+ btf_files=()
201+ for dir in $( find ./archive/ -iregex " .*${a} .*" -type d | sed ' s:\.\/archive\/::g' | sort -u) ; do
202+ while IFS= read -r -d ' ' file; do
203+ btf_files+=(" ${file} " )
204+ done < <( find " ./archive/${dir} " -name " *.tar.xz" -print0)
205+ done
206+
207+ total_files=${# btf_files[@]}
208+ echo " Found ${total_files} BTF files to process"
209+
210+ if [[ ${total_files} -eq 0 ]]; then
211+ echo " No BTF files found for architecture ${a} "
212+ exit 0
213+ fi
214+
215+ # Show system info
216+ echo " System: $( nproc) CPU cores, $( free -h | awk ' /^Mem:/ {print $2}' ) RAM"
217+ echo " Started at: $( date) "
218+ echo
81219
82- # generate one output BTF file to each input BTF file given
83- $btfgen gen min_core_btf ${extracted} ${out_dir} /${extracted} ${obj_cmdline}
84- [[ $ret -eq 0 ]] && [[ -f ./${extracted} ]] && rm -f ./${extracted}
220+ # Process files in parallel with job control
221+ for file in " ${btf_files[@]} " ; do
222+ # Wait if we've reached the maximum number of parallel jobs
223+ while [[ ${job_count} -ge ${j} ]]; do
224+ # Wait for any background job to complete
225+ wait -n
226+ exit_code=$?
227+ (( job_count-- ))
228+ (( completed_jobs++ ))
229+
230+ # Track failed jobs
231+ if [[ ${exit_code} -ne 0 ]]; then
232+ (( failed_jobs++ ))
233+ fi
85234 done
235+
236+ # Start new background job
237+ process_btf_file " ${file} " " ${btfgen} " " ${obj_cmdline} " &
238+ pid=$!
239+ background_pids+=(" ${pid} " )
240+ (( job_count++ ))
241+ done
242+
243+ # Wait for all remaining background jobs to complete
244+ echo -e " \nWaiting for remaining ${job_count} jobs to complete..."
245+ while [[ ${job_count} -gt 0 ]]; do
246+ wait -n
247+ exit_code=$?
248+ (( job_count-- ))
249+ (( completed_jobs++ ))
250+
251+ if [[ ${exit_code} -ne 0 ]]; then
252+ (( failed_jobs++ ))
253+ fi
86254done
255+
256+ # Final summary with timing
257+ end_time=" $( date +%s) "
258+ total_elapsed=$(( end_time - start_time))
259+ average_rate=$(( total_files * 60 / (total_elapsed + 1 )) )
260+
261+ echo -e " \n\n🎉 BTF processing completed!"
262+ echo " 📊 Total files: ${total_files} "
263+ echo " ✅ Completed: ${completed_jobs} "
264+ echo " ❌ Failed jobs: ${failed_jobs} "
265+ echo " ⚙️ Parallel jobs: ${j} "
266+ echo " ⏱️ Total time: $(( total_elapsed / 60 )) m $(( total_elapsed % 60 )) s"
267+ echo " 🚀 Average rate: ${average_rate} files/min"
268+
269+ if [[ ${failed_jobs} -gt 0 ]]; then
270+ echo " error: Some BTF files failed to process"
271+ exit 1
272+ fi
0 commit comments