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);
}
}