Created
August 22, 2025 19:44
-
-
Save brianmichel/43026aeeba388695898c5d052ad2c7f6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Comprehensive Disk Benchmark Suite using Hyperfine | |
| # Compares SSD, APFS RAM Disk, and HFS+ RAM Disk performance | |
| set -e | |
| # Color codes | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| MAGENTA='\033[0;35m' | |
| CYAN='\033[0;36m' | |
| NC='\033[0m' | |
| # Configuration | |
| TIMESTAMP=$(date +%Y%m%d_%H%M%S) | |
| RESULTS_DIR="benchmark_results_${TIMESTAMP}" | |
| RAMDISK_SIZE=8388608 # 4GB | |
| TEST_FILE_SIZE="100M" | |
| NUM_SMALL_FILES=1000 | |
| echo -e "${GREEN}=== Hyperfine Disk Benchmark Suite ===${NC}" | |
| echo -e "${YELLOW}Results will be saved to: ${RESULTS_DIR}${NC}\n" | |
| # Check for required tools | |
| check_requirements() { | |
| echo -e "${CYAN}Checking requirements...${NC}" | |
| if ! command -v hyperfine &> /dev/null; then | |
| echo -e "${RED}hyperfine not found. Installing...${NC}" | |
| brew install hyperfine | |
| fi | |
| if ! command -v fio &> /dev/null; then | |
| echo -e "${RED}fio not found. Installing...${NC}" | |
| brew install fio | |
| fi | |
| echo -e "${GREEN}All requirements satisfied${NC}\n" | |
| } | |
| # Create results directory | |
| mkdir -p "$RESULTS_DIR" | |
| # Setup test directories | |
| setup_test_dirs() { | |
| echo -e "${CYAN}Setting up test directories...${NC}" | |
| # SSD test directory | |
| SSD_TEST_DIR="/tmp/benchmark_ssd" | |
| mkdir -p "$SSD_TEST_DIR" | |
| # Create APFS RAM disk | |
| echo "Creating APFS RAM disk..." | |
| APFS_DEV=$(hdiutil attach -nomount ram://$RAMDISK_SIZE) | |
| diskutil erasevolume APFS "BenchAPFS" $APFS_DEV > /dev/null 2>&1 | |
| APFS_TEST_DIR="/Volumes/BenchAPFS/test" | |
| mkdir -p "$APFS_TEST_DIR" | |
| # Create HFS+ RAM disk | |
| echo "Creating HFS+ RAM disk..." | |
| HFS_DEV=$(hdiutil attach -nomount ram://$RAMDISK_SIZE) | |
| diskutil erasevolume HFS+ "BenchHFS" $HFS_DEV > /dev/null 2>&1 | |
| HFS_TEST_DIR="/Volumes/BenchHFS/test" | |
| mkdir -p "$HFS_TEST_DIR" | |
| echo -e "${GREEN}Test directories ready${NC}\n" | |
| # Export for use in tests | |
| export SSD_TEST_DIR APFS_TEST_DIR HFS_TEST_DIR | |
| } | |
| # Cleanup function | |
| cleanup() { | |
| echo -e "\n${YELLOW}Cleaning up...${NC}" | |
| rm -rf "$SSD_TEST_DIR" | |
| if mount | grep -q "BenchAPFS"; then | |
| diskutil eject /Volumes/BenchAPFS > /dev/null 2>&1 | |
| fi | |
| if mount | grep -q "BenchHFS"; then | |
| diskutil eject /Volumes/BenchHFS > /dev/null 2>&1 | |
| fi | |
| } | |
| trap cleanup EXIT | |
| # Test 1: FIO Random Read (simplified without job file) | |
| test_fio_random_read() { | |
| echo -e "${BLUE}=== Test 1: FIO Random 4K Read ===${NC}" | |
| # Create wrapper scripts for FIO | |
| cat > "$RESULTS_DIR/fio_read_ssd.sh" << EOF | |
| #!/bin/bash | |
| fio --name=randread --rw=randread --bs=4k --numjobs=4 --iodepth=32 \ | |
| --size=256M --runtime=5 --time_based --ioengine=posixaio \ | |
| --group_reporting --minimal --directory=$SSD_TEST_DIR 2>/dev/null | |
| EOF | |
| cat > "$RESULTS_DIR/fio_read_apfs.sh" << EOF | |
| #!/bin/bash | |
| fio --name=randread --rw=randread --bs=4k --numjobs=4 --iodepth=32 \ | |
| --size=256M --runtime=5 --time_based --ioengine=posixaio \ | |
| --group_reporting --minimal --directory=$APFS_TEST_DIR 2>/dev/null | |
| EOF | |
| cat > "$RESULTS_DIR/fio_read_hfs.sh" << EOF | |
| #!/bin/bash | |
| fio --name=randread --rw=randread --bs=4k --numjobs=4 --iodepth=32 \ | |
| --size=256M --runtime=5 --time_based --ioengine=posixaio \ | |
| --group_reporting --minimal --directory=$HFS_TEST_DIR 2>/dev/null | |
| EOF | |
| chmod +x "$RESULTS_DIR"/fio_read_*.sh | |
| # Run with hyperfine, ignoring FIO exit codes | |
| hyperfine \ | |
| --warmup 1 \ | |
| --runs 3 \ | |
| --ignore-failure \ | |
| --export-json "$RESULTS_DIR/fio_randread.json" \ | |
| --export-markdown "$RESULTS_DIR/fio_randread.md" \ | |
| --command-name 'SSD' \ | |
| "$RESULTS_DIR/fio_read_ssd.sh" \ | |
| --command-name 'APFS-RAM' \ | |
| "$RESULTS_DIR/fio_read_apfs.sh" \ | |
| --command-name 'HFS+-RAM' \ | |
| "$RESULTS_DIR/fio_read_hfs.sh" | |
| echo "" | |
| } | |
| # Test 2: FIO Random Write (simplified) | |
| test_fio_random_write() { | |
| echo -e "${BLUE}=== Test 2: FIO Random 4K Write ===${NC}" | |
| # Create wrapper scripts for FIO | |
| cat > "$RESULTS_DIR/fio_write_ssd.sh" << EOF | |
| #!/bin/bash | |
| fio --name=randwrite --rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \ | |
| --size=256M --runtime=5 --time_based --ioengine=posixaio \ | |
| --group_reporting --minimal --directory=$SSD_TEST_DIR 2>/dev/null | |
| EOF | |
| cat > "$RESULTS_DIR/fio_write_apfs.sh" << EOF | |
| #!/bin/bash | |
| fio --name=randwrite --rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \ | |
| --size=256M --runtime=5 --time_based --ioengine=posixaio \ | |
| --group_reporting --minimal --directory=$APFS_TEST_DIR 2>/dev/null | |
| EOF | |
| cat > "$RESULTS_DIR/fio_write_hfs.sh" << EOF | |
| #!/bin/bash | |
| fio --name=randwrite --rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \ | |
| --size=256M --runtime=5 --time_based --ioengine=posixaio \ | |
| --group_reporting --minimal --directory=$HFS_TEST_DIR 2>/dev/null | |
| EOF | |
| chmod +x "$RESULTS_DIR"/fio_write_*.sh | |
| hyperfine \ | |
| --warmup 1 \ | |
| --runs 3 \ | |
| --ignore-failure \ | |
| --export-json "$RESULTS_DIR/fio_randwrite.json" \ | |
| --export-markdown "$RESULTS_DIR/fio_randwrite.md" \ | |
| --command-name 'SSD' \ | |
| "$RESULTS_DIR/fio_write_ssd.sh" \ | |
| --command-name 'APFS-RAM' \ | |
| "$RESULTS_DIR/fio_write_apfs.sh" \ | |
| --command-name 'HFS+-RAM' \ | |
| "$RESULTS_DIR/fio_write_hfs.sh" | |
| echo "" | |
| } | |
| # Test 3: Small File Creation (Xcode-like) | |
| test_small_file_creation() { | |
| echo -e "${BLUE}=== Test 3: Create $NUM_SMALL_FILES Small Files ===${NC}" | |
| # Create test scripts | |
| cat > "$RESULTS_DIR/create_files.sh" << 'EOF' | |
| #!/bin/bash | |
| DIR=$1 | |
| mkdir -p "$DIR" | |
| for i in $(seq 1 1000); do | |
| echo "#include <stdio.h>" > "$DIR/file_$i.h" | |
| done | |
| EOF | |
| chmod +x "$RESULTS_DIR/create_files.sh" | |
| hyperfine \ | |
| --warmup 1 \ | |
| --runs 5 \ | |
| --prepare "rm -rf $SSD_TEST_DIR/file_* $APFS_TEST_DIR/file_* $HFS_TEST_DIR/file_*" \ | |
| --export-json "$RESULTS_DIR/small_files.json" \ | |
| --export-markdown "$RESULTS_DIR/small_files.md" \ | |
| --command-name 'SSD' \ | |
| "$RESULTS_DIR/create_files.sh $SSD_TEST_DIR" \ | |
| --command-name 'APFS-RAM' \ | |
| "$RESULTS_DIR/create_files.sh $APFS_TEST_DIR" \ | |
| --command-name 'HFS+-RAM' \ | |
| "$RESULTS_DIR/create_files.sh $HFS_TEST_DIR" | |
| echo "" | |
| } | |
| # Test 4: Compilation Simulation | |
| test_compilation_sim() { | |
| echo -e "${BLUE}=== Test 4: Compilation I/O Pattern ===${NC}" | |
| cat > "$RESULTS_DIR/compile_sim.sh" << 'EOF' | |
| #!/bin/bash | |
| DIR=$1 | |
| mkdir -p "$DIR" | |
| # Simulate reading headers | |
| for i in $(seq 1 100); do | |
| if [ -f "$DIR/file_$i.h" ]; then | |
| cat "$DIR/file_$i.h" > /dev/null 2>&1 | |
| else | |
| echo "test" > "$DIR/file_$i.h" | |
| fi | |
| done | |
| # Simulate writing object files | |
| for i in $(seq 1 50); do | |
| dd if=/dev/zero of="$DIR/obj_$i.o" bs=64k count=1 2>/dev/null | |
| done | |
| # Simulate linking (large sequential write) | |
| dd if=/dev/zero of="$DIR/binary" bs=1M count=10 2>/dev/null | |
| EOF | |
| chmod +x "$RESULTS_DIR/compile_sim.sh" | |
| hyperfine \ | |
| --warmup 2 \ | |
| --runs 5 \ | |
| --prepare "rm -rf $SSD_TEST_DIR/* $APFS_TEST_DIR/* $HFS_TEST_DIR/*" \ | |
| --export-json "$RESULTS_DIR/compile_sim.json" \ | |
| --export-markdown "$RESULTS_DIR/compile_sim.md" \ | |
| --command-name 'SSD' \ | |
| "$RESULTS_DIR/compile_sim.sh $SSD_TEST_DIR" \ | |
| --command-name 'APFS-RAM' \ | |
| "$RESULTS_DIR/compile_sim.sh $APFS_TEST_DIR" \ | |
| --command-name 'HFS+-RAM' \ | |
| "$RESULTS_DIR/compile_sim.sh $HFS_TEST_DIR" | |
| echo "" | |
| } | |
| # Test 5: Directory Operations | |
| test_directory_ops() { | |
| echo -e "${BLUE}=== Test 5: Directory Operations ===${NC}" | |
| cat > "$RESULTS_DIR/dir_ops.sh" << 'EOF' | |
| #!/bin/bash | |
| DIR=$1 | |
| mkdir -p "$DIR" | |
| # Create nested directories | |
| for i in $(seq 1 50); do | |
| mkdir -p "$DIR/level1/level2/level3/dir_$i" | |
| done | |
| # List directories | |
| find "$DIR" -type d > /dev/null | |
| # Remove directories | |
| rm -rf "$DIR/level1" | |
| EOF | |
| chmod +x "$RESULTS_DIR/dir_ops.sh" | |
| hyperfine \ | |
| --warmup 1 \ | |
| --runs 5 \ | |
| --prepare "rm -rf $SSD_TEST_DIR/* $APFS_TEST_DIR/* $HFS_TEST_DIR/*" \ | |
| --export-json "$RESULTS_DIR/dir_ops.json" \ | |
| --export-markdown "$RESULTS_DIR/dir_ops.md" \ | |
| --command-name 'SSD' \ | |
| "$RESULTS_DIR/dir_ops.sh $SSD_TEST_DIR" \ | |
| --command-name 'APFS-RAM' \ | |
| "$RESULTS_DIR/dir_ops.sh $APFS_TEST_DIR" \ | |
| --command-name 'HFS+-RAM' \ | |
| "$RESULTS_DIR/dir_ops.sh $HFS_TEST_DIR" | |
| echo "" | |
| } | |
| # Test 6: Large File Sequential Write | |
| test_sequential_write() { | |
| echo -e "${BLUE}=== Test 6: Sequential Write (1GB) ===${NC}" | |
| hyperfine \ | |
| --warmup 1 \ | |
| --runs 3 \ | |
| --prepare "rm -f $SSD_TEST_DIR/large $APFS_TEST_DIR/large $HFS_TEST_DIR/large" \ | |
| --export-json "$RESULTS_DIR/seq_write.json" \ | |
| --export-markdown "$RESULTS_DIR/seq_write.md" \ | |
| --command-name 'SSD' \ | |
| "dd if=/dev/zero of=$SSD_TEST_DIR/large bs=1M count=1024 2>/dev/null" \ | |
| --command-name 'APFS-RAM' \ | |
| "dd if=/dev/zero of=$APFS_TEST_DIR/large bs=1M count=1024 2>/dev/null" \ | |
| --command-name 'HFS+-RAM' \ | |
| "dd if=/dev/zero of=$HFS_TEST_DIR/large bs=1M count=1024 2>/dev/null" | |
| echo "" | |
| } | |
| # Test 7: Simple DD-based tests (more reliable) | |
| test_simple_io() { | |
| echo -e "${BLUE}=== Test 7: Simple I/O Tests ===${NC}" | |
| # 4K random read simulation | |
| echo -e "${CYAN}4K Random Reads (1000 operations)${NC}" | |
| cat > "$RESULTS_DIR/random_read.sh" << 'EOF' | |
| #!/bin/bash | |
| DIR=$1 | |
| # Create test file if it doesn't exist | |
| if [ ! -f "$DIR/testfile" ]; then | |
| dd if=/dev/zero of="$DIR/testfile" bs=1M count=100 2>/dev/null | |
| fi | |
| # Random reads | |
| for i in $(seq 1 1000); do | |
| dd if="$DIR/testfile" of=/dev/null bs=4k count=1 skip=$((RANDOM % 1000)) 2>/dev/null | |
| done | |
| EOF | |
| chmod +x "$RESULTS_DIR/random_read.sh" | |
| hyperfine \ | |
| --warmup 1 \ | |
| --runs 3 \ | |
| --export-json "$RESULTS_DIR/simple_randread.json" \ | |
| --export-markdown "$RESULTS_DIR/simple_randread.md" \ | |
| --command-name 'SSD' \ | |
| "$RESULTS_DIR/random_read.sh $SSD_TEST_DIR" \ | |
| --command-name 'APFS-RAM' \ | |
| "$RESULTS_DIR/random_read.sh $APFS_TEST_DIR" \ | |
| --command-name 'HFS+-RAM' \ | |
| "$RESULTS_DIR/random_read.sh $HFS_TEST_DIR" | |
| echo "" | |
| } | |
| # Generate combined report | |
| generate_report() { | |
| echo -e "${MAGENTA}=== Generating Combined Report ===${NC}" | |
| cat > "$RESULTS_DIR/report.md" << EOF | |
| # Disk Benchmark Results | |
| **Date:** $(date) | |
| **System:** $(uname -a) | |
| **Test Locations:** | |
| - SSD: $SSD_TEST_DIR | |
| - APFS RAM: $APFS_TEST_DIR | |
| - HFS+ RAM: $HFS_TEST_DIR | |
| ## Summary | |
| EOF | |
| # Append all markdown results | |
| for md_file in "$RESULTS_DIR"/*.md; do | |
| if [ "$md_file" != "$RESULTS_DIR/report.md" ]; then | |
| echo "### $(basename $md_file .md)" >> "$RESULTS_DIR/report.md" | |
| cat "$md_file" >> "$RESULTS_DIR/report.md" | |
| echo "" >> "$RESULTS_DIR/report.md" | |
| fi | |
| done | |
| # Create CSV summary | |
| echo -e "${CYAN}Creating CSV summary...${NC}" | |
| cat > "$RESULTS_DIR/summary.csv" << EOF | |
| Test,SSD_Mean,APFS_RAM_Mean,HFS_RAM_Mean,Unit | |
| EOF | |
| # Parse JSON results for CSV (requires jq) | |
| if command -v jq &> /dev/null; then | |
| for json_file in "$RESULTS_DIR"/*.json; do | |
| if [ -f "$json_file" ]; then | |
| TEST_NAME=$(basename "$json_file" .json) | |
| SSD_MEAN=$(jq '.results[0].mean' "$json_file" 2>/dev/null || echo "N/A") | |
| APFS_MEAN=$(jq '.results[1].mean' "$json_file" 2>/dev/null || echo "N/A") | |
| HFS_MEAN=$(jq '.results[2].mean' "$json_file" 2>/dev/null || echo "N/A") | |
| echo "$TEST_NAME,$SSD_MEAN,$APFS_MEAN,$HFS_MEAN,seconds" >> "$RESULTS_DIR/summary.csv" | |
| fi | |
| done | |
| fi | |
| echo -e "${GREEN}Report saved to: $RESULTS_DIR/report.md${NC}" | |
| echo -e "${GREEN}CSV summary saved to: $RESULTS_DIR/summary.csv${NC}" | |
| } | |
| # Debug function to test individual components | |
| debug_test() { | |
| echo -e "${YELLOW}=== Debug Mode ===${NC}" | |
| echo "Testing FIO installation..." | |
| which fio || echo "FIO not found!" | |
| fio --version || echo "FIO version check failed!" | |
| echo -e "\nTesting directories:" | |
| echo "SSD_TEST_DIR: $SSD_TEST_DIR" | |
| ls -la "$SSD_TEST_DIR" 2>/dev/null || echo "SSD dir not accessible" | |
| echo "APFS_TEST_DIR: $APFS_TEST_DIR" | |
| ls -la "$APFS_TEST_DIR" 2>/dev/null || echo "APFS dir not accessible" | |
| echo "HFS_TEST_DIR: $HFS_TEST_DIR" | |
| ls -la "$HFS_TEST_DIR" 2>/dev/null || echo "HFS dir not accessible" | |
| echo -e "\nTrying simple FIO command:" | |
| fio --name=test --rw=randread --bs=4k --size=10M --runtime=1 \ | |
| --directory="$SSD_TEST_DIR" || echo "Simple FIO test failed" | |
| } | |
| # Main execution | |
| main() { | |
| check_requirements | |
| setup_test_dirs | |
| # Optional debug mode | |
| if [ "$1" == "--debug" ]; then | |
| debug_test | |
| exit 0 | |
| fi | |
| # Run tests based on arguments or all | |
| if [ "$1" == "--quick" ]; then | |
| echo -e "${YELLOW}Running quick tests only...${NC}" | |
| test_small_file_creation | |
| test_compilation_sim | |
| test_simple_io | |
| else | |
| # Run all tests | |
| test_fio_random_read | |
| test_fio_random_write | |
| test_small_file_creation | |
| test_compilation_sim | |
| test_directory_ops | |
| test_sequential_write | |
| test_simple_io | |
| fi | |
| # Generate report | |
| generate_report | |
| echo -e "\n${GREEN}=== All Benchmarks Complete ===${NC}" | |
| echo -e "${YELLOW}Results saved in: $RESULTS_DIR/${NC}" | |
| echo -e "${CYAN}View the report: cat $RESULTS_DIR/report.md${NC}" | |
| echo -e "${CYAN}View CSV summary: cat $RESULTS_DIR/summary.csv${NC}" | |
| # Show quick summary | |
| echo -e "\n${MAGENTA}Quick Summary:${NC}" | |
| if [ -f "$RESULTS_DIR/small_files.md" ]; then | |
| echo -e "${YELLOW}Small File Creation Results:${NC}" | |
| tail -n 4 "$RESULTS_DIR/small_files.md" | |
| fi | |
| } | |
| # Run main | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment