1 contributor
#!/usr/bin/perl
=head1 NAME
test-differential-storage.pl - Test differential SMART storage system
=head1 DESCRIPTION
This script tests the differential storage implementation by:
1. Creating test HDD entries
2. Inserting baseline SMART readings
3. Inserting identical readings (should be skipped)
4. Inserting readings with small changes (differential storage)
5. Inserting readings with critical changes (full storage)
6. Validating storage efficiency and reconstruction
=cut
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use DBI;
use JSON::XS;
use Data::Dumper;
use Time::HiRes qw(time);
use Digest::SHA;
# Database configuration
my $config = {
db_host => $ENV{AUTOSMART_DB_HOST} || 'localhost',
db_port => $ENV{AUTOSMART_DB_PORT} || '5432',
db_name => $ENV{AUTOSMART_DB_NAME} || 'autosmart',
db_user => $ENV{AUTOSMART_DB_USER} || 'autosmart',
db_pass => $ENV{AUTOSMART_DB_PASS} || 'smartpassword',
};
print "=== autoSMART Differential Storage Test ===\n\n";
# Connect to database
my $dsn = "DBI:Pg:dbname=$config->{db_name};host=$config->{db_host};port=$config->{db_port}";
my $dbh = DBI->connect($dsn, $config->{db_user}, $config->{db_pass}, {
RaiseError => 1,
AutoCommit => 1,
PrintError => 0
}) or die "Failed to connect to database: $DBI::errstr\n";
print "✓ Connected to database\n";
# Clean up any existing test data
cleanup_test_data($dbh);
# Test 1: Create test HDD
my $test_hdd_id = create_test_hdd($dbh);
print "✓ Created test HDD (ID: $test_hdd_id)\n";
# Test 2: Insert baseline reading
my $baseline_reading = {
parameters => {
'Reallocated_Sector_Ct' => 0,
'Spin_Retry_Count' => 0,
'Current_Pending_Sector' => 0,
'Power_On_Hours' => 1000,
'Temperature_Celsius' => 35,
'Load_Cycle_Count' => 5000
},
temperature => 35
};
my $baseline_id = insert_test_reading($dbh, $test_hdd_id, $baseline_reading);
print "✓ Inserted baseline reading (ID: $baseline_id)\n";
# Test 3: Insert identical reading (should be skipped)
sleep(1);
my $identical_result = test_should_store($dbh, $test_hdd_id, $baseline_reading);
print "✓ Identical reading test - Should store: " .
($identical_result->{should_store} ? "YES" : "NO") .
" (Type: $identical_result->{reading_type})\n";
# Test 4: Insert reading with temperature change only (differential)
my $temp_change_reading = {
%$baseline_reading,
temperature => 38
};
$temp_change_reading->{parameters}{Temperature_Celsius} = 38;
sleep(1);
my $temp_result = test_should_store($dbh, $test_hdd_id, $temp_change_reading);
my $temp_id = insert_test_reading($dbh, $test_hdd_id, $temp_change_reading, $temp_result);
print "✓ Temperature change reading - Should store: " .
($temp_result->{should_store} ? "YES" : "NO") .
" (Type: $temp_result->{reading_type}, ID: $temp_id)\n";
# Test 5: Insert reading with critical parameter change (full)
my $critical_reading = {
%$baseline_reading,
temperature => 40
};
$critical_reading->{parameters}{Reallocated_Sector_Ct} = 1; # Critical parameter change
$critical_reading->{parameters}{Temperature_Celsius} = 40;
sleep(1);
my $critical_result = test_should_store($dbh, $test_hdd_id, $critical_reading);
my $critical_id = insert_test_reading($dbh, $test_hdd_id, $critical_reading, $critical_result);
print "✓ Critical change reading - Should store: " .
($critical_result->{should_store} ? "YES" : "NO") .
" (Type: $critical_result->{reading_type}, ID: $critical_id)\n";
# Test 6: Validate reconstruction
print "\n--- Testing Data Reconstruction ---\n";
test_reconstruction($dbh, $test_hdd_id);
# Test 7: Show storage statistics
print "\n--- Storage Statistics ---\n";
show_storage_stats($dbh, $test_hdd_id);
print "\n=== Test Complete ===\n";
$dbh->disconnect();
sub cleanup_test_data {
my ($dbh) = @_;
$dbh->do("DELETE FROM smart_readings WHERE serial_number = 'TEST_SERIAL_001'");
$dbh->do("DELETE FROM hdd_inventory WHERE serial_number = 'TEST_SERIAL_001'");
}
sub create_test_hdd {
my ($dbh) = @_;
my $sql = q{
INSERT INTO hdd_inventory
(serial_number, model_name, firmware, size_gb, manufacturer,
current_device_path, current_node_id, status)
VALUES ('TEST_SERIAL_001', 'TEST_MODEL_WD', '1.0', 1000, 'Western Digital',
'/dev/sdb', 'test-node', 'active')
RETURNING id
};
my $sth = $dbh->prepare($sql);
$sth->execute();
return $sth->fetchrow_array();
}
sub test_should_store {
my ($dbh, $hdd_id, $reading) = @_;
my $parameters_json = encode_json($reading->{parameters});
my $checksum = Digest::SHA::sha256_hex($parameters_json . ($reading->{temperature} || ''));
my $sth = $dbh->prepare(q{
SELECT should_store_smart_reading(?, ?, ?, NOW())
});
$sth->execute($hdd_id, $parameters_json, $checksum);
return $sth->fetchrow_hashref();
}
sub insert_test_reading {
my ($dbh, $hdd_id, $reading, $storage_info) = @_;
# If no storage info provided, get it
if (!$storage_info) {
$storage_info = test_should_store($dbh, $hdd_id, $reading);
return undef unless $storage_info->{should_store};
}
return undef unless $storage_info->{should_store};
# For differential readings, only store changed parameters
my $parameters_to_store;
if ($storage_info->{reading_type} eq 'differential' && $storage_info->{changed_parameters}) {
my $changed_params = decode_json($storage_info->{changed_parameters});
$parameters_to_store = {};
for my $param_name (@$changed_params) {
$parameters_to_store->{$param_name} = $reading->{parameters}{$param_name};
}
} else {
$parameters_to_store = $reading->{parameters};
}
my $sql = q{
INSERT INTO smart_readings
(hdd_id, serial_number, device_path, node_id, timestamp,
collection_ok, temperature, parameters_json, reading_type,
changes_detected, changed_parameters, previous_reading_id, checksum)
VALUES (?, ?, ?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id
};
my $parameters_json = encode_json($parameters_to_store);
my $checksum = Digest::SHA::sha256_hex(encode_json($reading->{parameters}) . ($reading->{temperature} || ''));
my $sth = $dbh->prepare($sql);
$sth->execute(
$hdd_id,
'TEST_SERIAL_001',
'/dev/sdb',
'test-node',
1, # collection_ok
$reading->{temperature},
$parameters_json,
$storage_info->{reading_type},
$storage_info->{changes_detected} ? 1 : 0,
$storage_info->{changed_parameters},
$storage_info->{previous_reading_id},
$checksum
);
return $sth->fetchrow_array();
}
sub test_reconstruction {
my ($dbh, $hdd_id) = @_;
my $sql = q{
SELECT id, timestamp, reading_type, chain_level, parameters_json, temperature
FROM smart_readings_reconstructed
WHERE hdd_id = ?
ORDER BY timestamp
};
my $sth = $dbh->prepare($sql);
$sth->execute($hdd_id);
while (my $row = $sth->fetchrow_hashref()) {
print "Reading ID: $row->{id}, Type: $row->{reading_type}, Chain: $row->{chain_level}\n";
print " Temperature: $row->{temperature}°C\n";
my $params = decode_json($row->{parameters_json});
for my $param (sort keys %$params) {
print " $param: $params->{$param}\n";
}
print "\n";
}
}
sub show_storage_stats {
my ($dbh, $hdd_id) = @_;
my $sql = q{
SELECT
reading_type,
COUNT(*) as count,
AVG(length(parameters_json::text)) as avg_size
FROM smart_readings
WHERE hdd_id = ?
GROUP BY reading_type
ORDER BY reading_type
};
my $sth = $dbh->prepare($sql);
$sth->execute($hdd_id);
my $total_readings = 0;
my $total_size = 0;
while (my $row = $sth->fetchrow_hashref()) {
printf "%-12s: %d readings, avg size: %.0f bytes\n",
$row->{reading_type}, $row->{count}, $row->{avg_size};
$total_readings += $row->{count};
$total_size += $row->{count} * $row->{avg_size};
}
print "\nTotal: $total_readings readings, estimated size: " . int($total_size) . " bytes\n";
}