import {revive, persist, labelInput, labelSelect} from '/js/util.js'; export default { id:'zfs', name:'ZFS Calculator', about:'Calculate ZFS pool configurations, performance tuning, and capacity planning.', render(root){ const key='calc_zfs_v1'; const s = revive(key,{ poolType: 'raidz2', diskCount: 6, diskSize: 4000, diskSizeUnit: 'GB', blockSize: '128K', compression: 'lz4', dedup: false, ashift: 12, arcMax: 8192, arcMaxUnit: 'MB' }); const ui = document.createElement('div'); // Pool configuration section const poolSection = document.createElement('div'); poolSection.innerHTML = `

Pool Configuration

`; // Performance tuning section const perfSection = document.createElement('div'); perfSection.innerHTML = `

Performance Tuning

`; ui.append(poolSection, perfSection); // Results section const out = document.createElement('div'); out.className = 'result'; out.style.cssText = ` margin: 20px 0; padding: 15px; background: var(--k-bg); border-radius: 8px; border-left: 4px solid var(--accent); `; ui.append(out); // Calculation function function calc(){ const poolType = ui.querySelector('[name=poolType]').value; const diskCount = +ui.querySelector('[name=diskCount]').value; const diskSize = +ui.querySelector('[name=diskSize]').value; const diskSizeUnit = ui.querySelector('[name=diskSizeUnit]').value; const blockSize = ui.querySelector('[name=blockSize]').value; const compression = ui.querySelector('[name=compression]').value; const dedup = ui.querySelector('[name=dedup]').value === 'true'; const ashift = +ui.querySelector('[name=ashift]').value; const arcMax = +ui.querySelector('[name=arcMax]').value; const arcMaxUnit = ui.querySelector('[name=arcMaxUnit]').value; // Convert disk size to GB for calculations let diskSizeGB = diskSize; if (diskSizeUnit === 'TB') { diskSizeGB = diskSize * 1024; } // Calculate usable capacity based on pool type let usableCapacity, redundancy, minDisks, recommendedDisks, vdevCount; switch(poolType) { case 'stripe': usableCapacity = diskCount * diskSizeGB; redundancy = 'None'; minDisks = 1; recommendedDisks = 1; vdevCount = 1; break; case 'mirror': usableCapacity = Math.floor(diskCount / 2) * diskSizeGB; redundancy = '50%'; minDisks = 2; recommendedDisks = 2; vdevCount = Math.floor(diskCount / 2); break; case 'raidz1': usableCapacity = (diskCount - 1) * diskSizeGB; redundancy = '1 disk'; minDisks = 3; recommendedDisks = 3; vdevCount = 1; break; case 'raidz2': usableCapacity = (diskCount - 2) * diskSizeGB; redundancy = '2 disks'; minDisks = 4; recommendedDisks = 6; vdevCount = 1; break; case 'raidz3': usableCapacity = (diskCount - 3) * diskSizeGB; redundancy = '3 disks'; minDisks = 5; recommendedDisks = 9; vdevCount = 1; break; case 'mirror2x2': usableCapacity = 2 * diskSizeGB; // 2 mirrors, each with 1 usable disk redundancy = '50%'; minDisks = 4; recommendedDisks = 4; vdevCount = 2; break; case 'mirror3x2': usableCapacity = 3 * diskSizeGB; // 3 mirrors, each with 1 usable disk redundancy = '50%'; minDisks = 6; recommendedDisks = 6; vdevCount = 3; break; case 'raidz2x2': usableCapacity = 2 * (diskCount / 2 - 2) * diskSizeGB; // 2 RAID-Z2 vdevs redundancy = '2 disks per vdev'; minDisks = 8; recommendedDisks = 12; vdevCount = 2; break; } // Validate disk count if (diskCount < minDisks) { out.innerHTML = `
Error: ${poolType.toUpperCase()} requires at least ${minDisks} disks
`; return; } // Calculate compression ratios const compressionRatios = { 'off': 1.0, 'lz4': 2.1, 'gzip': 2.5, 'gzip-1': 2.0, 'gzip-9': 3.0, 'zstd': 2.8, 'zstd-1': 2.2, 'zstd-19': 3.5 }; const compressionRatio = compressionRatios[compression] || 1.0; const effectiveCapacity = usableCapacity * compressionRatio; // Calculate performance metrics const blockSizeKB = parseInt(blockSize.replace(/[^0-9]/g, '')); const ashiftBytes = Math.pow(2, ashift); // Convert ARC max to MB let arcMaxMB = arcMax; if (arcMaxUnit === 'GB') { arcMaxMB = arcMax * 1024; } // Helper function to format capacity function formatCapacity(gb) { if (gb >= 1024) { return `${gb.toFixed(1)} GB (${(gb / 1024).toFixed(2)} TB)`; } return `${gb.toFixed(1)} GB`; } // Calculate I/O performance estimates const estimatedIOPS = Math.floor(diskCount * 100); // Rough estimate: 100 IOPS per disk const estimatedThroughput = Math.floor(diskCount * 150); // Rough estimate: 150 MB/s per disk // Calculate memory requirements (rule of thumb: 1GB RAM per 1TB storage) const recommendedRAM = Math.ceil((diskCount * diskSizeGB) / 1024); // Generate results out.innerHTML = `
ZFS Pool Configuration

Capacity & Redundancy

Raw Capacity: ${formatCapacity(diskCount * diskSizeGB)}
Usable Capacity: ${formatCapacity(usableCapacity)}
Effective Capacity (with compression): ${formatCapacity(effectiveCapacity)}
Redundancy: ${redundancy}
Efficiency: ${((usableCapacity / (diskCount * diskSizeGB)) * 100).toFixed(1)}%
Compression Savings: ${((effectiveCapacity - usableCapacity) / usableCapacity * 100).toFixed(1)}%
VDev Count: ${vdevCount} ${vdevCount > 1 ? 'vdevs' : 'vdev'}

Performance Settings

Block Size: ${blockSize}
Ashift: ${ashift} (${ashiftBytes} bytes)
Compression: ${compression} (${compressionRatio.toFixed(1)}x ratio)
Deduplication: ${dedup ? 'On' : 'Off'}
ARC Max: ${arcMaxMB} MB

Performance Estimates

Estimated IOPS: ${estimatedIOPS.toLocaleString()} (random 4K reads)
Estimated Throughput: ${estimatedThroughput} MB/s (sequential)
Recommended RAM: ${recommendedRAM} GB minimum
Current ARC: ${(arcMaxMB / 1024).toFixed(1)} GB

System Requirements

Minimum RAM: ${Math.max(8, recommendedRAM)} GB
Recommended RAM: ${Math.max(16, recommendedRAM * 2)} GB
CPU Cores: ${Math.max(4, Math.ceil(diskCount / 2))} cores recommended
Network: 10 Gbps recommended for ${estimatedThroughput} MB/s throughput
`; persist(key, {poolType, diskCount, diskSize, diskSizeUnit, blockSize, compression, dedup, ashift, arcMax, arcMaxUnit}); } // Event listeners ui.querySelector('[name=poolType]').addEventListener('change', calc); ui.querySelector('[name=diskCount]').addEventListener('input', calc); ui.querySelector('[name=diskSize]').addEventListener('input', calc); ui.querySelector('[name=diskSizeUnit]').addEventListener('change', calc); ui.querySelector('[name=blockSize]').addEventListener('change', calc); ui.querySelector('[name=compression]').addEventListener('change', calc); ui.querySelector('[name=dedup]').addEventListener('change', calc); ui.querySelector('[name=ashift]').addEventListener('change', calc); ui.querySelector('[name=arcMax]').addEventListener('input', calc); ui.querySelector('[name=arcMaxUnit]').addEventListener('change', calc); // Initial calculation calc(); root.append(ui); } }