#!/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';
Generated: " . strftime("%Y-%m-%d %H:%M:%S", localtime($data->{generated_at})) . "
\n"; # Basic HTML output - could be expanded significantly print $fh "" . encode_json($data) . "\n"; print $fh "\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