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 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_disk(): command = ["iostat", "-xyd", "1", "1"] 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 scaleIostat_cpu(): command = ["iostat", "-c", "1", "1"] 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 convert_to_megabytes(data_size): try: unit = data_size[-1].upper() size = data_size[:-1] size = float(size) except (ValueError, AttributeError): return None unit_map = { "K": 1 / (1024**1), "M": 1, "G": 1024, "T": 1024**2, } if unit not in unit_map: return None return int(size * unit_map[unit]) def scaleMemstat(): command = ["top", "-bn", "1"] filename = "memStat.csv" collect = runCollect(command) if collect: mem = re.search("Mem :.*", collect).group() mem = re.sub("Mem..", "", mem.strip()) mem = re.sub("[total|free|used|buff|cache|,|/]", "", mem.strip()).split() meminmeg = [] for x in mem: meminmeg.append(convert_to_megabytes(f"{x}M")) mem = meminmeg mem_free = mem[1] mem_used = mem[2] lineData = [mem_used, mem_free] if lineData: with open(filename, "a", newline="") as csvfile: csv_writer = csv.writer(csvfile) lineData.insert(0, "mem") lineData.insert(0, getTimestamp()) csv_writer.writerow(lineData) else: print(f"Error running command: {collect}") def coreMemstat(): command = ["top", "-n", "1"] filename = "memStat.csv" collect = runCollect(command) if collect: mem = re.search("Mem:.*", collect).group() mem = re.sub("Mem.", "", mem.strip()) mem = re.sub("[Active|Inact|Wired|Free|,]", "", mem.strip()).split() meminmeg = [] for x in mem: meminmeg.append(convert_to_megabytes(x)) mem = meminmeg mem_used = mem[0] mem_free = mem[3] lineData = [mem_used, mem_free] if lineData: with open(filename, "a", newline="") as csvfile: csv_writer = csv.writer(csvfile) lineData.insert(0, "mem") lineData.insert(0, getTimestamp()) csv_writer.writerow(lineData) else: print(f"Error running command: {collect}") def ifstat(): command = ["ifstat", "-znq", "1", "1"] 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 coreCPUstat(): command = ["iostat", "-C", "-t", "proc", "-d"] filename = "cpuStat.csv" collect = runCollect(command) if collect: # process collection string bylines = collect.split("\n") lineData = bylines[2].split() lineData = lineData[:-2] + [0] + lineData[-2:] 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 zpoolIostat(): command = ["zpool", "iostat", "-Tu", "-l", "-p", "-v", "-y", "15", "1"] 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): if is_freebsd(): gstat_command = ["gstat", "-C", "-s", "-d", "-o", "-p", "-I", "5s"] 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) for i in range(minutes): if i == 0: print("Minute:", end="", flush=True) print(f" {i}", end="", flush=True) zpoolIostat() ifstat() if is_debian(): scaleIostat_cpu() scaleIostat_disk() scaleMemstat() if is_freebsd(): coreCPUstat() coreMemstat() if i == minutes: print("") break time.sleep(60) # 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() 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-perf-{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 collects cpu/mem/disk/network/pool #") 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 is_two_digit_number(number): return 10 <= number < 100 def main(): welcome() minutes = int(input("Enter the duration in minutes: ")) startHour = 0 while not is_two_digit_number(startHour): startHour = int( input("Enter start time on 24hr format, 2-digit (ie 2pm = 14): ") ) if not is_two_digit_number(startTime): print("please enter only 2 digits.") currentTime = datetime.datetime.now() currentHour = currentTime.hour while not currentHour == startHour: print(f"Current Time: {currentTime}, script will start at: {startHour}:00") datetime.datetime.sleep(5) currentTime = datetime.datetime.now() currentHour = currentTime.hour print("Starting Collection") # Collect data collect_data(minutes) # 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()