import os
import time
import random
import argparse
import csv
import subprocess
from multiprocessing import Pool, cpu_count
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
class FusePerfTester:
def __init__(self, test_path, file_size='1M', output='report'):
self.test_path = test_path
self.file_size = self.parse_size(file_size)
self.output_dir = output
self.stats = {
'write': {'time': [], 'speed': []},
'read': {'time': [], 'speed': []},
'random_read': {'iops': []},
'metadata': {'create': [], 'delete': [], 'stat': []}
}
os.makedirs(self.output_dir, exist_ok=True)
@staticmethod
def parse_size(size_str):
units = {"B": 1, "K": 2**10, "M": 2**20, "G": 2**30}
num = int(''.join(filter(str.isdigit, size_str)))
unit = ''.join(filter(str.isalpha, size_str.upper())) or 'B'
return num * units[unit]
def _cleanup(self):
subprocess.run(['rm', '-rf', os.path.join(self.test_path, 'testfile*')],
check=True)
def sequential_write(self, iterations=10):
for i in range(iterations):
filename = os.path.join(self.test_path, f'testfile_write_{i}.dat')
start = time.time()
with open(filename, 'wb') as f:
f.write(os.urandom(self.file_size))
elapsed = time.time() - start
speed = self.file_size / elapsed / (1024**2) self.stats['write']['time'].append(elapsed)
self.stats['write']['speed'].append(speed)
def sequential_read(self, iterations=10):
test_file = os.path.join(self.test_path, 'testfile_read.dat')
with open(test_file, 'wb') as f:
f.write(os.urandom(self.file_size))
for _ in range(iterations):
start = time.time()
with open(test_file, 'rb') as f:
f.read()
elapsed = time.time() - start
speed = self.file_size / elapsed / (1024**2)
self.stats['read']['time'].append(elapsed)
self.stats['read']['speed'].append(speed)
def random_io_test(self, block_size=4096, duration=30):
test_file = os.path.join(self.test_path, 'testfile_random.dat')
file_size = 1 * 1024**3 with open(test_file, 'wb') as f:
f.truncate(file_size)
blocks = file_size // block_size
count = 0
start = time.time()
while time.time() - start < duration:
with open(test_file, 'rb+') as f:
offset = random.randint(0, blocks-1) * block_size
f.seek(offset)
f.write(os.urandom(block_size))
count += 1
self.stats['random_read']['iops'].append(count // duration)
def metadata_ops(self, iterations=1000):
for i in range(iterations//3):
start = time.time()
with open(os.path.join(self.test_path, f'temp_{i}.dat'), 'w') as f:
f.write('test')
self.stats['metadata']['create'].append(time.time() - start)
start = time.time()
os.stat(os.path.join(self.test_path, f'temp_{i}.dat'))
self.stats['metadata']['stat'].append(time.time() - start)
start = time.time()
os.remove(os.path.join(self.test_path, f'temp_{i}.dat'))
self.stats['metadata']['delete'].append(time.time() - start)
def concurrent_test(self, workers=cpu_count()):
test_file = os.path.join(self.test_path, 'concurrent.dat')
file_size = 100 * 1024**2 with open(test_file, 'wb') as f:
f.truncate(file_size)
def worker(_):
block_size = 4096
offset = random.randint(0, (file_size//block_size)-1) * block_size
data = os.urandom(block_size)
with open(test_file, 'r+b') as f:
f.seek(offset)
f.write(data)
start = time.time()
with Pool(workers) as p:
p.map(worker, range(1000))
elapsed = time.time() - start
self.stats['concurrency'] = {'total_time': elapsed, 'workers': workers}
def generate_report(self):
sns.set(style="whitegrid")
plt.figure(figsize=(12, 6))
write_speeds = self.stats['write']['speed']
read_speeds = self.stats['read']['speed']
sns.lineplot(x=range(len(write_speeds)), y=write_speeds, label='Write Speed (MB/s)')
sns.lineplot(x=range(len(read_speeds)), y=read_speeds, label='Read Speed (MB/s)')
plt.title('Sequential R/W Throughput')
plt.savefig(os.path.join(self.output_dir, 'throughput.png'))
plt.close()
plt.figure(figsize=(10,5))
data = [
np.mean(self.stats['metadata']['create']) * 1000,
np.mean(self.stats['metadata']['stat']) * 1000,
np.mean(self.stats['metadata']['delete']) * 1000
]
labels = ['Create', 'Stat', 'Delete']
sns.barplot(x=labels, y=data)
plt.ylabel('Latency (ms)')
plt.title('Metadata Operation Latency')
plt.savefig(os.path.join(self.output_dir, 'metadata.png'))
plt.close()
with open(os.path.join(self.output_dir, 'summary.csv'), 'w') as f:
writer = csv.writer(f)
writer.writerow(['Test Item', 'Average', 'Max', 'Min', 'Std'])
for test in ['write', 'read']:
data = self.stats[test]['speed']
writer.writerow([
f"{test.capitalize()} Speed (MB/s)",
np.mean(data),
max(data),
min(data),
np.std(data)
])
if 'concurrency' in self.stats:
writer.writerow([
'Concurrency (ops/s)',
1000 / self.stats['concurrency']['total_time'],
'', '', ''
])
self._cleanup()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='FUSE Filesystem Performance Tester')
parser.add_argument('path', help='FUSE mount path to test')
parser.add_argument('-s', '--size', default='128M',
help='Test file size (e.g. 1M, 512K)')
parser.add_argument('-o', '--output', default='report',
help='Output directory for reports')
args = parser.parse_args()
tester = FusePerfTester(args.path, args.size, args.output)
print("Running sequential write test...")
tester.sequential_write()
print("Running sequential read test...")
tester.sequential_read()
print("Running random I/O test...")
tester.random_io_test()
print("Testing metadata operations...")
tester.metadata_ops()
print("Testing concurrency...")
tester.concurrent_test()
print("Generating report...")
tester.generate_report()