f16725e 3 months ago History
1 contributor
662 lines | 17.463kb
#!/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