1 contributor
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use Getopt::Long;
use Config::Simple;
use JSON::XS;
use POSIX qw(strftime);
=head1 NAME
autosmart-report.pl - Generate comprehensive reports for autoSMART system
=head1 SYNOPSIS
autosmart-report.pl [OPTIONS]
=head1 OPTIONS
--config-dir DIR Configuration directory (default: /etc/autosmart)
--report TYPE Report type: summary, detailed, health, alerts, trends
--device PATH Report for specific device only
--days N Days of history to include (default: 30)
--format FORMAT Output format: text, html, json (default: text)
--output FILE Write to file instead of stdout
--help Show this help
=head1 DESCRIPTION
Generate various reports from autoSMART data including drive health summaries,
detailed SMART analysis, alert history, and trend analysis.
=cut
# Configuration
my $config_dir = '/etc/autosmart';
my $report_type = 'summary';
my $specific_device = '';
my $days = 30;
my $format = 'text';
my $output_file = '';
my $help = 0;
GetOptions(
'config-dir=s' => \$config_dir,
'report=s' => \$report_type,
'device=s' => \$specific_device,
'days=i' => \$days,
'format=s' => \$format,
'output=s' => \$output_file,
'help' => \$help,
) or die "Error parsing command line arguments\n";
if ($help) {
print_help();
exit 0;
}
# Validate options
unless ($report_type =~ /^(summary|detailed|health|alerts|trends)$/) {
die "Invalid report type: $report_type\n";
}
unless ($format =~ /^(text|html|json)$/) {
die "Invalid format: $format\n";
}
# Connect to database
my $db_config = "$config_dir/database.conf";
unless (-f $db_config) {
die "Database configuration not found: $db_config\n";
}
my $cfg = Config::Simple->new($db_config);
my $dsn = sprintf("DBI:Pg:database=%s;host=%s;port=%s",
$cfg->param('database.database'),
$cfg->param('database.host'),
$cfg->param('database.port')
);
my $dbh = DBI->connect(
$dsn,
$cfg->param('database.username'),
$cfg->param('database.password'),
{ RaiseError => 1, AutoCommit => 1, pg_enable_utf8 => 1 }
) or die "Database connection failed: $DBI::errstr";
# Generate report
my $report_data = generate_report($dbh, $report_type, $specific_device, $days);
# Output report
my $output_handle = \*STDOUT;
if ($output_file) {
open $output_handle, '>', $output_file
or die "Cannot open output file $output_file: $!\n";
}
if ($format eq 'json') {
output_json($output_handle, $report_data);
} elsif ($format eq 'html') {
output_html($output_handle, $report_data, $report_type);
} else {
output_text($output_handle, $report_data, $report_type);
}
close $output_handle if $output_file;
$dbh->disconnect();
=head2 generate_report
Generate report data based on type
=cut
sub generate_report {
my ($dbh, $type, $device, $days) = @_;
my $data = {
report_type => $type,
generated_at => time(),
days_included => $days,
specific_device => $device,
};
if ($type eq 'summary') {
$data->{summary} = get_system_summary($dbh, $days);
} elsif ($type eq 'detailed') {
$data->{drives} = get_detailed_drive_info($dbh, $device, $days);
} elsif ($type eq 'health') {
$data->{health} = get_health_overview($dbh, $device);
} elsif ($type eq 'alerts') {
$data->{alerts} = get_alert_history($dbh, $device, $days);
} elsif ($type eq 'trends') {
$data->{trends} = get_trend_analysis($dbh, $device, $days);
}
return $data;
}
=head2 get_system_summary
Get high-level system summary
=cut
sub get_system_summary {
my ($dbh, $days) = @_;
my $summary = {};
# Drive counts by status
my $sth = $dbh->prepare(q{
SELECT status, COUNT(*) as count
FROM hdd_inventory
GROUP BY status
});
$sth->execute();
$summary->{drive_counts} = {};
while (my $row = $sth->fetchrow_hashref()) {
$summary->{drive_counts}->{$row->{status}} = $row->{count};
}
# Recent predictions summary
$sth = $dbh->prepare(q{
SELECT risk_level, COUNT(*) as count
FROM predictions
WHERE timestamp >= NOW() - INTERVAL ? DAY
GROUP BY risk_level
});
$sth->execute($days);
$summary->{recent_predictions} = {};
while (my $row = $sth->fetchrow_hashref()) {
$summary->{recent_predictions}->{$row->{risk_level}} = $row->{count};
}
# Recent alerts
$sth = $dbh->prepare(q{
SELECT alert_type, COUNT(*) as count
FROM alert_history
WHERE sent_at >= NOW() - INTERVAL ? DAY
GROUP BY alert_type
});
$sth->execute($days);
$summary->{recent_alerts} = {};
while (my $row = $sth->fetchrow_hashref()) {
$summary->{recent_alerts}->{$row->{alert_type}} = $row->{count};
}
# Data collection stats
$sth = $dbh->prepare(q{
SELECT
COUNT(*) as total_readings,
COUNT(DISTINCT device_path) as devices_with_data,
AVG(CASE WHEN collection_ok THEN 1.0 ELSE 0.0 END) * 100 as success_rate
FROM smart_readings
WHERE timestamp >= NOW() - INTERVAL ? DAY
});
$sth->execute($days);
if (my $row = $sth->fetchrow_hashref()) {
$summary->{collection_stats} = {
total_readings => $row->{total_readings},
devices_with_data => $row->{devices_with_data},
success_rate => sprintf("%.1f", $row->{success_rate} || 0),
};
}
return $summary;
}
=head2 get_detailed_drive_info
Get detailed information for drives
=cut
sub get_detailed_drive_info {
my ($dbh, $device, $days) = @_;
my $sql = q{
SELECT
hi.device_path,
hi.model_name,
hi.serial_number,
hi.size_gb,
hi.status,
hi.first_seen,
hi.last_seen,
COUNT(sr.id) as reading_count,
AVG(sr.temperature) as avg_temperature,
MAX(sr.temperature) as max_temperature
FROM hdd_inventory hi
LEFT JOIN smart_readings sr ON hi.device_path = sr.device_path
AND sr.timestamp >= NOW() - INTERVAL ? DAY
};
my @params = ($days);
if ($device) {
$sql .= " WHERE hi.device_path = ?";
push @params, $device;
}
$sql .= q{
GROUP BY hi.device_path, hi.model_name, hi.serial_number,
hi.size_gb, hi.status, hi.first_seen, hi.last_seen
ORDER BY hi.device_path
};
my $sth = $dbh->prepare($sql);
$sth->execute(@params);
my @drives = ();
while (my $row = $sth->fetchrow_hashref()) {
# Get latest prediction
my $pred_sth = $dbh->prepare(q{
SELECT risk_level, confidence, timestamp
FROM predictions
WHERE device_path = ?
ORDER BY timestamp DESC
LIMIT 1
});
$pred_sth->execute($row->{device_path});
if (my $pred = $pred_sth->fetchrow_hashref()) {
$row->{latest_prediction} = $pred;
}
# Get recent alerts
my $alert_sth = $dbh->prepare(q{
SELECT COUNT(*) as alert_count
FROM alert_history
WHERE device_path = ?
AND sent_at >= NOW() - INTERVAL ? DAY
});
$alert_sth->execute($row->{device_path}, $days);
if (my $alert = $alert_sth->fetchrow_hashref()) {
$row->{recent_alert_count} = $alert->{alert_count};
}
push @drives, $row;
}
return \@drives;
}
=head2 get_health_overview
Get current health overview
=cut
sub get_health_overview {
my ($dbh, $device) = @_;
my $sql = q{
SELECT * FROM drive_health_summary
};
my @params = ();
if ($device) {
$sql .= " WHERE device_path = ?";
push @params, $device;
}
$sql .= " ORDER BY device_path";
my $sth = $dbh->prepare($sql);
$sth->execute(@params);
my @health_data = ();
while (my $row = $sth->fetchrow_hashref()) {
push @health_data, $row;
}
return \@health_data;
}
=head2 get_alert_history
Get alert history
=cut
sub get_alert_history {
my ($dbh, $device, $days) = @_;
my $sql = q{
SELECT
ah.device_path,
ah.alert_type,
ah.risk_level,
ah.message,
ah.sent_at,
ah.acknowledged,
ah.acknowledged_by,
hi.model_name
FROM alert_history ah
JOIN hdd_inventory hi ON ah.device_path = hi.device_path
WHERE ah.sent_at >= NOW() - INTERVAL ? DAY
};
my @params = ($days);
if ($device) {
$sql .= " AND ah.device_path = ?";
push @params, $device;
}
$sql .= " ORDER BY ah.sent_at DESC";
my $sth = $dbh->prepare($sql);
$sth->execute(@params);
my @alerts = ();
while (my $row = $sth->fetchrow_hashref()) {
push @alerts, $row;
}
return \@alerts;
}
=head2 get_trend_analysis
Get trend analysis data
=cut
sub get_trend_analysis {
my ($dbh, $device, $days) = @_;
# This is a simplified trend analysis
# In production, you might want more sophisticated analysis
my $sql = q{
SELECT
device_path,
DATE(timestamp) as date,
AVG(temperature) as avg_temp,
COUNT(*) as reading_count
FROM smart_readings
WHERE timestamp >= NOW() - INTERVAL ? DAY
};
my @params = ($days);
if ($device) {
$sql .= " AND device_path = ?";
push @params, $device;
}
$sql .= q{
GROUP BY device_path, DATE(timestamp)
ORDER BY device_path, date
};
my $sth = $dbh->prepare($sql);
$sth->execute(@params);
my %trends = ();
while (my $row = $sth->fetchrow_hashref()) {
push @{$trends{$row->{device_path}}}, {
date => $row->{date},
avg_temp => sprintf("%.1f", $row->{avg_temp} || 0),
reading_count => $row->{reading_count},
};
}
return \%trends;
}
=head2 output_text
Output report as text
=cut
sub output_text {
my ($fh, $data, $type) = @_;
print $fh "\n" . "="x80 . "\n";
print $fh "autoSMART System Report - " . ucfirst($type) . "\n";
print $fh "Generated: " . strftime("%Y-%m-%d %H:%M:%S", localtime($data->{generated_at})) . "\n";
print $fh "Time Period: Last $data->{days_included} days\n";
print $fh "="x80 . "\n\n";
if ($type eq 'summary') {
output_summary_text($fh, $data->{summary});
} elsif ($type eq 'detailed') {
output_detailed_text($fh, $data->{drives});
} elsif ($type eq 'health') {
output_health_text($fh, $data->{health});
} elsif ($type eq 'alerts') {
output_alerts_text($fh, $data->{alerts});
} elsif ($type eq 'trends') {
output_trends_text($fh, $data->{trends});
}
}
=head2 output_summary_text
Output summary in text format
=cut
sub output_summary_text {
my ($fh, $summary) = @_;
print $fh "SYSTEM OVERVIEW\n";
print $fh "-"x40 . "\n";
print $fh "Drive Status:\n";
foreach my $status (sort keys %{$summary->{drive_counts}}) {
printf $fh " %-10s: %d drives\n", ucfirst($status), $summary->{drive_counts}->{$status};
}
if (%{$summary->{recent_predictions}}) {
print $fh "\nRecent Risk Predictions:\n";
foreach my $level (qw(critical high moderate low)) {
next unless $summary->{recent_predictions}->{$level};
printf $fh " %-10s: %d drives\n", ucfirst($level), $summary->{recent_predictions}->{$level};
}
}
if (%{$summary->{recent_alerts}}) {
print $fh "\nRecent Alerts:\n";
foreach my $type (sort keys %{$summary->{recent_alerts}}) {
printf $fh " %-15s: %d alerts\n", $type, $summary->{recent_alerts}->{$type};
}
}
if ($summary->{collection_stats}) {
my $stats = $summary->{collection_stats};
print $fh "\nData Collection:\n";
print $fh " Total readings: $stats->{total_readings}\n";
print $fh " Devices monitored: $stats->{devices_with_data}\n";
print $fh " Success rate: $stats->{success_rate}%\n";
}
print $fh "\n";
}
=head2 output_json
Output report as JSON
=cut
sub output_json {
my ($fh, $data) = @_;
my $json = JSON::XS->new->pretty->encode($data);
print $fh $json;
}
=head2 output_html
Output report as HTML (basic implementation)
=cut
sub output_html {
my ($fh, $data, $type) = @_;
print $fh <<'EOF';
<!DOCTYPE html>
<html>
<head>
<title>autoSMART Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.critical { color: #d32f2f; font-weight: bold; }
.high { color: #f57c00; font-weight: bold; }
.moderate { color: #fbc02d; }
.low { color: #388e3c; }
</style>
</head>
<body>
EOF
print $fh "<h1>autoSMART Report - " . ucfirst($type) . "</h1>\n";
print $fh "<p>Generated: " . strftime("%Y-%m-%d %H:%M:%S", localtime($data->{generated_at})) . "</p>\n";
# Basic HTML output - could be expanded significantly
print $fh "<pre>" . encode_json($data) . "</pre>\n";
print $fh "</body></html>\n";
}
# Additional text output functions would go here...
sub output_detailed_text {
my ($fh, $drives) = @_;
# Implementation for detailed drive output
print $fh "DETAILED DRIVE INFORMATION\n";
print $fh "-"x40 . "\n";
foreach my $drive (@$drives) {
print $fh "Device: $drive->{device_path}\n";
print $fh "Model: " . ($drive->{model_name} || 'Unknown') . "\n";
print $fh "Serial: " . ($drive->{serial_number} || 'Unknown') . "\n";
print $fh "Status: $drive->{status}\n";
if ($drive->{latest_prediction}) {
print $fh "Latest Risk: $drive->{latest_prediction}->{risk_level}\n";
}
print $fh "\n";
}
}
sub output_health_text {
my ($fh, $health) = @_;
print $fh "DRIVE HEALTH OVERVIEW\n";
print $fh "-"x40 . "\n";
foreach my $drive (@$health) {
print $fh "$drive->{device_path}: $drive->{status}";
print $fh " (Risk: $drive->{risk_level})" if $drive->{risk_level};
print $fh "\n";
}
}
sub output_alerts_text {
my ($fh, $alerts) = @_;
print $fh "ALERT HISTORY\n";
print $fh "-"x40 . "\n";
foreach my $alert (@$alerts) {
printf $fh "%s [%s] %s: %s\n",
$alert->{sent_at},
$alert->{alert_type},
$alert->{device_path},
$alert->{message} || '';
}
}
sub output_trends_text {
my ($fh, $trends) = @_;
print $fh "TREND ANALYSIS\n";
print $fh "-"x40 . "\n";
foreach my $device (sort keys %$trends) {
print $fh "Device: $device\n";
foreach my $trend (@{$trends->{$device}}) {
print $fh " $trend->{date}: Temp $trend->{avg_temp}°C ($trend->{reading_count} readings)\n";
}
print $fh "\n";
}
}
=head2 print_help
Display help information
=cut
sub print_help {
print <<'EOF';
autoSMART Report Generator v1.0
USAGE:
autosmart-report.pl [OPTIONS]
OPTIONS:
--config-dir DIR Configuration directory (default: /etc/autosmart)
--report TYPE Report type (default: summary)
summary - System overview and statistics
detailed - Detailed drive information
health - Current health status of all drives
alerts - Alert history
trends - Trend analysis
--device PATH Generate report for specific device only
--days N Days of history to include (default: 30)
--format FORMAT Output format: text, html, json (default: text)
--output FILE Write to file instead of stdout
--help Show this help message
EXAMPLES:
# System summary
autosmart-report.pl --report summary
# Detailed report for specific drive
autosmart-report.pl --report detailed --device /dev/sda
# Health status as HTML
autosmart-report.pl --report health --format html --output health.html
# Alert history for last week
autosmart-report.pl --report alerts --days 7
# Trend analysis as JSON
autosmart-report.pl --report trends --format json
REPORT TYPES:
summary High-level system statistics and overview
detailed Comprehensive information about each drive
health Current health status summary
alerts Recent alerts and notifications
trends Temperature and performance trends
OUTPUT FORMATS:
text Human-readable text format (default)
html HTML report with basic styling
json Machine-readable JSON format
EOF
}
__END__
=head1 AUTHOR
AutoSMART Development Team
=head1 LICENSE
This software is part of the autoSMART project.
=cut