import subprocess import time import re import csv import os import shutil import socket import platform import tarfile from datetime import datetime def is_freebsd(): return platform.system() == "FreeBSD" def is_debian(): # Debian-based systems often have 'debian' in their platform string return "linux" in platform.platform().lower() def scaleIostat_disk(): command = ["iostat", "-xyd", "1", "1"] headerRow = [ "timestamp", "name", "read_s", "rkB_s", "rrqm_s", "%rrqm", "r_await", "rareq-sz", "w_s", "wkB_s", "wrqm_s", "%wrqm", "w_await", "wareq-sz", "d_s", "dkB_s", "drqm_s", "%drqm", "d_await", "dareq-sz", "f_s", "f_await", "aqu-sz", "%utilread_s", ] filename = "ioStat.csv" collect = runCollect(command) if collect: byline = re.split("\n", collect.strip()) data = byline[4:] for line in data: lineData = line.split() if lineData: with open(filename, "a", newline="") as csvfile: csv_writer = csv.writer(csvfile) lineData.insert(0, getTimestamp()) csv_writer.writerow(lineData) else: print(f"Error running command: {collect}") def getTimestamp(): timestamp = time.time() local_time = time.localtime(timestamp) # Format the time components for a more readable output return time.strftime("%Y-%m-%d %H:%M:%S", local_time) def runCollect(command): # Run the command and capture output result = subprocess.run(command, capture_output=True, text=True) # Check the return code (0 for success) if result.returncode == 0: # Access the captured output as a string return result.stdout return result.stderr def scaleIostat_cpu(): command = ["iostat", "-c", "1", "1"] headerRow = [ "timestamp", "name", "%user", "%nice", "%system", "%iowait", "%steal", "%idle", ] filename = "cpuStat.csv" collect = runCollect(command) if collect: byline = re.split("\n", collect.strip()) data = byline[3].strip() lineData = data.split() if lineData: with open(filename, "a", newline="") as csvfile: csv_writer = csv.writer(csvfile) lineData.insert(0, "cpu") lineData.insert(0, getTimestamp()) csv_writer.writerow(lineData) else: print(f"Error running command: {collect}") def ifstat(): command = ["ifstat", "-znq", "1", "1"] headerRow = [ "timestamp", "name", "Kbps_in", "Kbps_out", ] filename = "ifStat.csv" collect = runCollect(command) if collect: # process collection string bylines = collect.split("\n") interfaces = bylines[0].split() stats = bylines[2].split() for nic in interfaces: lineData = [getTimestamp(), nic, stats.pop(0), stats.pop(0)] if lineData: with open(filename, "a", newline="") as csvfile: csv_writer = csv.writer(csvfile) csv_writer.writerow(lineData) else: print(f"Error running command: {collect}") def zpoolIostat(): command = ["zpool", "iostat", "-Tu", "-l", "-p", "-v", "-y", "15", "1"] headerRow = [ "timestamp", "name", "capacity_alloc", "capacity_free", "ops_read", "ops_write", "bandwidth_read", "bandwidth_write", "total_wait_read", "total_wait_write", "disk_wait_read", "disk_wait_write", "syncq_wait_read", "syncq_wait_write", "asyncq_wait_read", "asyncq_wait_write", "scrub_wait", "trim_wait", "rebuld_wait", ] collect = runCollect(command) if collect: # split output by pools pools = re.split(r"^-", collect, flags=re.MULTILINE) i = 0 for pool in pools: if i == 0: pass else: # remove ------ lines pool = re.sub("^---.*", "", pool).strip() # turn - values into 0 pool = re.sub(" -", "0", pool) y = 0 poolbyline = re.split("\n", pool) for line in poolbyline: lineData = line.split() if not lineData: break if y == 0: poolname = lineData[0] filename = poolname + "-zio.csv" # Now open the file in append mode ('a') with open(filename, "a", newline="") as csvfile: csv_writer = csv.writer(csvfile) lineData.insert(0, getTimestamp()) csv_writer.writerow(lineData) y += 1 i += 1 else: print(f"Error running command: {collect}") def collect_data(minutes, interval): start_time = datetime.now() # Continuously collect data for the specified duration gstat_command = ["gstat", "-C", "-s", "-d", "-o", "-p", "-I", "5s"] if is_freebsd(): with open("gstat.csv", "a") as output_file: # Create a Popen object with stdout redirected to the file process = subprocess.Popen(gstat_command, stdout=output_file) x = 1 for _ in range(minutes * 60 // interval): # Collect data using system commands zpoolIostat() ifstat() if is_debian(): scaleIostat_cpu() scaleIostat_disk() # Print progress indicator elapsed_time = (datetime.now() - start_time).total_seconds() // interval if x == 1: print("Minute:", end="", flush=True) x = 0 print(f" {int(elapsed_time + 1)}", end="", flush=True) time.sleep(interval) print("") # kill gstat if freebsd if is_freebsd(): process.kill() def run_debug(): print("Taking new debug.") command = ["midclt", "call", "system.debug_generate", "-job"] try: result = subprocess.run(command, capture_output=True, text=True, check=True) return result.stdout except subprocess.CalledProcessError as e: print(f"Error running midclt debug: {e}") return None # def collect_csv(): # source_dir = os.getcwd() # tar_file_name = "dpkg.log" # Choose a desired name for the tar file # tar_file_path = os.path.join(source_dir, tar_file_name) # with tarfile.open(tar_file_path, "w:gz") as tar: # for filename in os.listdir(source_dir): # if filename.endswith(".csv"): # source_file = os.path.join(source_dir, filename) # try: # tar.add(source_file, arcname=os.path.basename(source_file)) # print(f"Added '{filename}' to tar archive successfully.") # except FileNotFoundError: # print(f"Error: File '{filename}' not found in '{source_dir}'.") # except Exception as e: # Catch other potential errors # print(f"Error adding '{filename}' to tar archive: {e}") # return tar_file_path def collect_csv(): source_dir = os.getcwd() if is_debian(): destination_dir = "/var/log/proftpd" if is_freebsd(): destination_dir = "/var/log" for filename in os.listdir(source_dir): if filename.endswith(".csv"): # Check for .csv extension source_file = os.path.join(source_dir, filename) destination_file = os.path.join(destination_dir, filename) # Handle potential errors (e.g., source file not found, permission issues) try: shutil.copy2(source_file, destination_file) print(f"Copied '{filename}' to /var/log successfully.") except FileNotFoundError: print(f"Error: File '{filename}' not found in '{source_dir}'.") except PermissionError: print(f"Error: Insufficient permissions to copy '{filename}'.") except Exception as e: # Catch other potential errors print(f"Error copying '{filename}': {e}") def upload_debug(): """ Uploads debug file to a specified FTP server. """ hostname = socket.gethostname() # Generate debug file debug_file = run_debug().strip() # Upload data files subprocess.run( [ "curl", "--user", "customer:ixcustomer", "-T", debug_file, f"ftp.ixsystems.com/debug-{hostname}-{datetime.now().strftime('%Y%m%d%H%M')}.tgz", ] ) def welcome(): print("########################################################################") print("# FreeNAS CORE/SCALE performance capture script v.02 #") print("########################################################################") print("# This script runs for x minutes and collects cpu/disk/network stats #") print("# When it is completed, it will copy csv files to your /var/log folder #") print("# It will then attempt to take a debug and upload both to our FTP #") print("# If not connected to the internet, it will have to be manually d/l #") print("########################################################################") print("# Running this script repeatedly will append results to CSV files #") print("# - https://gitlab.komputernerds.com/mmance/statscollect - #") print("########################################################################") print("") def main(): welcome() minutes = int(input("Enter the duration in minutes: ")) interval = 60 # Default interval between data collection cycles (seconds) print("Starting Collection") # Collect data collect_data(minutes, interval) # Copy data files to /var/log (replace with appropriate copying function) collect_csv() # Upload data (replace with credentials and actual upload logic if needed) upload_debug() print("Data collection and upload completed.") if __name__ == "__main__": main()