#!/usr/bin/env perl # Copyright (c) 2026, rodolico # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # trimZFS - SSD optimization via TRIM operations on ZFS pools # # PURPOSE: # Performs TRIM operations on all ZFS pools to optimize SSD performance by # informing the storage device about unused blocks. Essential for maintaining # long-term SSD health and write performance. # # USAGE: # Standalone: ./trimZFS # From sneakernet: Executed automatically during cleanup phase on target server # From Perl script: my $results = eval { do './trimZFS' }; # # EXECUTION CONTEXT: # When executed by sneakernet on the target server: # - Script is read from transport drive, decrypted, and executed via eval() # - Progress messages sent to STDOUT for real-time monitoring # - Summary results returned via return statement for logging # - Exit code indicates success (0) or failure (1) when run standalone # # BEHAVIOR: # 1. Discovers all ZFS pools on the system # 2. Initiates TRIM operation on each pool sequentially # 3. Tracks execution time for each pool # 4. Collects success/failure status for each operation # 5. Returns summary report with statistics # # TRIM DURATION: # TRIM time depends on: # - Pool size and SSD capacity # - Amount of free space to TRIM # - SSD performance characteristics # Typical range: Seconds to minutes per pool # # REQUIREMENTS: # - ZFS utilities (zpool command with TRIM support) # - Root/sudo privileges for pool operations # - SSD-backed storage (TRIM has no effect on HDDs) # - Pools must be imported # # RECOMMENDATIONS: # - Run regularly (monthly recommended) for SSD health # - Safe to run on active systems # - TRIM is most effective when run during low I/O periods # # OUTPUT: # Real-time: Progress messages to STDOUT during TRIM operations # Summary: Success/failure count and timing for each pool (return value or STDOUT) # Exit code: 0 if all successful, 1 if any failures (standalone mode only) # # Author: rodolico # Version: 1.0 # Updated: 2026-01-16 use strict; use warnings; # Get list of all ZFS pools my @pools = `zpool list -H -o name`; chomp @pools; my @errorMessages = (); if (!@pools) { push @errorMessages, "No ZFS pools found"; if (caller()) { return ( "", join("\n", @errorMessages) ); } else { print "No ZFS pools found.\n"; exit 1; } } print "Starting ZFS trim on all pools...\n"; print "=" x 60 . "\n"; my %results; my @logsEntries; # Process each pool sequentially, waiting for each to complete foreach my $pool (@pools) { print "\nTrimming pool: $pool\n"; my $startTime = time; # Start trim operation my $output = `zpool trim $pool 2>&1`; my $exitCode = $? >> 8; if ($exitCode != 0) { my $duration = time - $startTime; $results{$pool} = { status => 'FAILED', duration => $duration, output => $output }; push @errorMessages, "Pool $pool\tTRIM FAILED to start: $output"; print " Status: FAILED to start (${duration}s)\n"; print " Error: $output\n" if $output; next; } print " Trim initiated, waiting for completion...\n"; # Wait for trim to complete by monitoring status my $trimActive = 1; my $checkCount = 0; while ($trimActive) { sleep 5; # Check every 5 seconds $checkCount++; # Get trim status for this pool my $statusOutput = `zpool status -t $pool 2>&1`; # Check if any trim operations are in progress # Look for "trimming" or "trim: " in the status output if ($statusOutput !~ /trim(?:ming|:)/i || $statusOutput =~ /trim:\s*none/i) { $trimActive = 0; } # Print progress indicator every 12 checks (1 minute) if ($checkCount % 12 == 0) { my $elapsed = time - $startTime; print " Still trimming... (${elapsed}s elapsed)\n"; } } my $duration = time - $startTime; # Verify completion status my $finalStatus = `zpool status -t $pool 2>&1`; my $success = 1; # Check for any trim errors if ($finalStatus =~ /trim.*error/i || $finalStatus =~ /FAULTED|DEGRADED/i) { $success = 0; } $results{$pool} = { status => $success ? 'SUCCESS' : 'FAILED', duration => $duration, output => $success ? "Trim completed successfully" : "Trim completed with errors" }; if ($success) { print " Status: SUCCESS (${duration}s)\n"; } else { push @errorMessages, "Pool $pool\tTRIM FAILED - completed with errors"; print " Status: FAILED (${duration}s)\n"; print " Check 'zpool status -t $pool' for details\n"; } } # Final report print "\n" . "=" x 60 . "\n"; print "TRIM SUMMARY REPORT\n"; print "=" x 60 . "\n"; my $successCount = 0; my $failedCount = 0; foreach my $pool (sort keys %results) { my $result = $results{$pool}; printf "%-20s : %s (%ds)\n", $pool, $result->{status}, $result->{duration}; if ($result->{status} eq 'SUCCESS') { $successCount++; } else { $failedCount++; } } push @logsEntries, "TRIM SUMMARY REPORT"; push @logsEntries, "=" x 60 . "\n"; foreach my $pool (sort keys %results) { my $result = $results{$pool}; push @logsEntries, sprintf "%-20s : %s (%ds)", $pool, $result->{status}, $result->{duration}; } push @logsEntries, "=" x 60; push @logsEntries, "Total pools: " . scalar(@pools); push @logsEntries, "Successful: $successCount"; push @logsEntries, "Failed: $failedCount"; # When run standalone, print to STDOUT if (caller()) { return ( join( "\n", @logsEntries ) . "\n", @errorMessages ? join("\n", @errorMessages) : "" ); } else { print "Results Summary:\n"; print join( "\n", @logsEntries ) . "\n"; if (@errorMessages) { print "\n=== CLEANUP SCRIPT ERRORS ===\n"; print join("\n", @errorMessages) . "\n"; } exit($failedCount > 0 ? 1 : 0); } # End of script