| 1 |
#!/usr/bin/perl |
|
| 2 | ||
| 3 |
use strict; |
|
| 4 |
use warnings; |
|
| 5 |
use DBI; |
|
| 6 |
use Getopt::Long; |
|
| 7 |
use Config::Simple; |
|
| 8 |
use JSON::XS; |
|
| 9 |
use POSIX qw(strftime); |
|
| 10 | ||
| 11 |
=head1 NAME |
|
| 12 | ||
| 13 |
autosmart-report.pl - Generate comprehensive reports for autoSMART system |
|
| 14 | ||
| 15 |
=head1 SYNOPSIS |
|
| 16 | ||
| 17 |
autosmart-report.pl [OPTIONS] |
|
| 18 | ||
| 19 |
=head1 OPTIONS |
|
| 20 | ||
| 21 |
--config-dir DIR Configuration directory (default: /etc/autosmart) |
|
| 22 |
--report TYPE Report type: summary, detailed, health, alerts, trends |
|
| 23 |
--device PATH Report for specific device only |
|
| 24 |
--days N Days of history to include (default: 30) |
|
| 25 |
--format FORMAT Output format: text, html, json (default: text) |
|
| 26 |
--output FILE Write to file instead of stdout |
|
| 27 |
--help Show this help |
|
| 28 | ||
| 29 |
=head1 DESCRIPTION |
|
| 30 | ||
| 31 |
Generate various reports from autoSMART data including drive health summaries, |
|
| 32 |
detailed SMART analysis, alert history, and trend analysis. |
|
| 33 | ||
| 34 |
=cut |
|
| 35 | ||
| 36 |
# Configuration |
|
| 37 |
my $config_dir = '/etc/autosmart'; |
|
| 38 |
my $report_type = 'summary'; |
|
| 39 |
my $specific_device = ''; |
|
| 40 |
my $days = 30; |
|
| 41 |
my $format = 'text'; |
|
| 42 |
my $output_file = ''; |
|
| 43 |
my $help = 0; |
|
| 44 | ||
| 45 |
GetOptions( |
|
| 46 |
'config-dir=s' => \$config_dir, |
|
| 47 |
'report=s' => \$report_type, |
|
| 48 |
'device=s' => \$specific_device, |
|
| 49 |
'days=i' => \$days, |
|
| 50 |
'format=s' => \$format, |
|
| 51 |
'output=s' => \$output_file, |
|
| 52 |
'help' => \$help, |
|
| 53 |
) or die "Error parsing command line arguments\n"; |
|
| 54 | ||
| 55 |
if ($help) {
|
|
| 56 |
print_help(); |
|
| 57 |
exit 0; |
|
| 58 |
} |
|
| 59 | ||
| 60 |
# Validate options |
|
| 61 |
unless ($report_type =~ /^(summary|detailed|health|alerts|trends)$/) {
|
|
| 62 |
die "Invalid report type: $report_type\n"; |
|
| 63 |
} |
|
| 64 | ||
| 65 |
unless ($format =~ /^(text|html|json)$/) {
|
|
| 66 |
die "Invalid format: $format\n"; |
|
| 67 |
} |
|
| 68 | ||
| 69 |
# Connect to database |
|
| 70 |
my $db_config = "$config_dir/database.conf"; |
|
| 71 |
unless (-f $db_config) {
|
|
| 72 |
die "Database configuration not found: $db_config\n"; |
|
| 73 |
} |
|
| 74 | ||
| 75 |
my $cfg = Config::Simple->new($db_config); |
|
| 76 |
my $dsn = sprintf("DBI:Pg:database=%s;host=%s;port=%s",
|
|
| 77 |
$cfg->param('database.database'),
|
|
| 78 |
$cfg->param('database.host'),
|
|
| 79 |
$cfg->param('database.port')
|
|
| 80 |
); |
|
| 81 | ||
| 82 |
my $dbh = DBI->connect( |
|
| 83 |
$dsn, |
|
| 84 |
$cfg->param('database.username'),
|
|
| 85 |
$cfg->param('database.password'),
|
|
| 86 |
{ RaiseError => 1, AutoCommit => 1, pg_enable_utf8 => 1 }
|
|
| 87 |
) or die "Database connection failed: $DBI::errstr"; |
|
| 88 | ||
| 89 |
# Generate report |
|
| 90 |
my $report_data = generate_report($dbh, $report_type, $specific_device, $days); |
|
| 91 | ||
| 92 |
# Output report |
|
| 93 |
my $output_handle = \*STDOUT; |
|
| 94 |
if ($output_file) {
|
|
| 95 |
open $output_handle, '>', $output_file |
|
| 96 |
or die "Cannot open output file $output_file: $!\n"; |
|
| 97 |
} |
|
| 98 | ||
| 99 |
if ($format eq 'json') {
|
|
| 100 |
output_json($output_handle, $report_data); |
|
| 101 |
} elsif ($format eq 'html') {
|
|
| 102 |
output_html($output_handle, $report_data, $report_type); |
|
| 103 |
} else {
|
|
| 104 |
output_text($output_handle, $report_data, $report_type); |
|
| 105 |
} |
|
| 106 | ||
| 107 |
close $output_handle if $output_file; |
|
| 108 | ||
| 109 |
$dbh->disconnect(); |
|
| 110 | ||
| 111 |
=head2 generate_report |
|
| 112 | ||
| 113 |
Generate report data based on type |
|
| 114 | ||
| 115 |
=cut |
|
| 116 | ||
| 117 |
sub generate_report {
|
|
| 118 |
my ($dbh, $type, $device, $days) = @_; |
|
| 119 | ||
| 120 |
my $data = {
|
|
| 121 |
report_type => $type, |
|
| 122 |
generated_at => time(), |
|
| 123 |
days_included => $days, |
|
| 124 |
specific_device => $device, |
|
| 125 |
}; |
|
| 126 | ||
| 127 |
if ($type eq 'summary') {
|
|
| 128 |
$data->{summary} = get_system_summary($dbh, $days);
|
|
| 129 |
} elsif ($type eq 'detailed') {
|
|
| 130 |
$data->{drives} = get_detailed_drive_info($dbh, $device, $days);
|
|
| 131 |
} elsif ($type eq 'health') {
|
|
| 132 |
$data->{health} = get_health_overview($dbh, $device);
|
|
| 133 |
} elsif ($type eq 'alerts') {
|
|
| 134 |
$data->{alerts} = get_alert_history($dbh, $device, $days);
|
|
| 135 |
} elsif ($type eq 'trends') {
|
|
| 136 |
$data->{trends} = get_trend_analysis($dbh, $device, $days);
|
|
| 137 |
} |
|
| 138 | ||
| 139 |
return $data; |
|
| 140 |
} |
|
| 141 | ||
| 142 |
=head2 get_system_summary |
|
| 143 | ||
| 144 |
Get high-level system summary |
|
| 145 | ||
| 146 |
=cut |
|
| 147 | ||
| 148 |
sub get_system_summary {
|
|
| 149 |
my ($dbh, $days) = @_; |
|
| 150 | ||
| 151 |
my $summary = {};
|
|
| 152 | ||
| 153 |
# Drive counts by status |
|
| 154 |
my $sth = $dbh->prepare(q{
|
|
| 155 |
SELECT status, COUNT(*) as count |
|
| 156 |
FROM hdd_inventory |
|
| 157 |
GROUP BY status |
|
| 158 |
}); |
|
| 159 |
$sth->execute(); |
|
| 160 | ||
| 161 |
$summary->{drive_counts} = {};
|
|
| 162 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 163 |
$summary->{drive_counts}->{$row->{status}} = $row->{count};
|
|
| 164 |
} |
|
| 165 | ||
| 166 |
# Recent predictions summary |
|
| 167 |
$sth = $dbh->prepare(q{
|
|
| 168 |
SELECT risk_level, COUNT(*) as count |
|
| 169 |
FROM predictions |
|
| 170 |
WHERE timestamp >= NOW() - INTERVAL ? DAY |
|
| 171 |
GROUP BY risk_level |
|
| 172 |
}); |
|
| 173 |
$sth->execute($days); |
|
| 174 | ||
| 175 |
$summary->{recent_predictions} = {};
|
|
| 176 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 177 |
$summary->{recent_predictions}->{$row->{risk_level}} = $row->{count};
|
|
| 178 |
} |
|
| 179 | ||
| 180 |
# Recent alerts |
|
| 181 |
$sth = $dbh->prepare(q{
|
|
| 182 |
SELECT alert_type, COUNT(*) as count |
|
| 183 |
FROM alert_history |
|
| 184 |
WHERE sent_at >= NOW() - INTERVAL ? DAY |
|
| 185 |
GROUP BY alert_type |
|
| 186 |
}); |
|
| 187 |
$sth->execute($days); |
|
| 188 | ||
| 189 |
$summary->{recent_alerts} = {};
|
|
| 190 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 191 |
$summary->{recent_alerts}->{$row->{alert_type}} = $row->{count};
|
|
| 192 |
} |
|
| 193 | ||
| 194 |
# Data collection stats |
|
| 195 |
$sth = $dbh->prepare(q{
|
|
| 196 |
SELECT |
|
| 197 |
COUNT(*) as total_readings, |
|
| 198 |
COUNT(DISTINCT device_path) as devices_with_data, |
|
| 199 |
AVG(CASE WHEN collection_ok THEN 1.0 ELSE 0.0 END) * 100 as success_rate |
|
| 200 |
FROM smart_readings |
|
| 201 |
WHERE timestamp >= NOW() - INTERVAL ? DAY |
|
| 202 |
}); |
|
| 203 |
$sth->execute($days); |
|
| 204 | ||
| 205 |
if (my $row = $sth->fetchrow_hashref()) {
|
|
| 206 |
$summary->{collection_stats} = {
|
|
| 207 |
total_readings => $row->{total_readings},
|
|
| 208 |
devices_with_data => $row->{devices_with_data},
|
|
| 209 |
success_rate => sprintf("%.1f", $row->{success_rate} || 0),
|
|
| 210 |
}; |
|
| 211 |
} |
|
| 212 | ||
| 213 |
return $summary; |
|
| 214 |
} |
|
| 215 | ||
| 216 |
=head2 get_detailed_drive_info |
|
| 217 | ||
| 218 |
Get detailed information for drives |
|
| 219 | ||
| 220 |
=cut |
|
| 221 | ||
| 222 |
sub get_detailed_drive_info {
|
|
| 223 |
my ($dbh, $device, $days) = @_; |
|
| 224 | ||
| 225 |
my $sql = q{
|
|
| 226 |
SELECT |
|
| 227 |
hi.device_path, |
|
| 228 |
hi.model_name, |
|
| 229 |
hi.serial_number, |
|
| 230 |
hi.size_gb, |
|
| 231 |
hi.status, |
|
| 232 |
hi.first_seen, |
|
| 233 |
hi.last_seen, |
|
| 234 |
COUNT(sr.id) as reading_count, |
|
| 235 |
AVG(sr.temperature) as avg_temperature, |
|
| 236 |
MAX(sr.temperature) as max_temperature |
|
| 237 |
FROM hdd_inventory hi |
|
| 238 |
LEFT JOIN smart_readings sr ON hi.device_path = sr.device_path |
|
| 239 |
AND sr.timestamp >= NOW() - INTERVAL ? DAY |
|
| 240 |
}; |
|
| 241 | ||
| 242 |
my @params = ($days); |
|
| 243 | ||
| 244 |
if ($device) {
|
|
| 245 |
$sql .= " WHERE hi.device_path = ?"; |
|
| 246 |
push @params, $device; |
|
| 247 |
} |
|
| 248 | ||
| 249 |
$sql .= q{
|
|
| 250 |
GROUP BY hi.device_path, hi.model_name, hi.serial_number, |
|
| 251 |
hi.size_gb, hi.status, hi.first_seen, hi.last_seen |
|
| 252 |
ORDER BY hi.device_path |
|
| 253 |
}; |
|
| 254 | ||
| 255 |
my $sth = $dbh->prepare($sql); |
|
| 256 |
$sth->execute(@params); |
|
| 257 | ||
| 258 |
my @drives = (); |
|
| 259 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 260 |
# Get latest prediction |
|
| 261 |
my $pred_sth = $dbh->prepare(q{
|
|
| 262 |
SELECT risk_level, confidence, timestamp |
|
| 263 |
FROM predictions |
|
| 264 |
WHERE device_path = ? |
|
| 265 |
ORDER BY timestamp DESC |
|
| 266 |
LIMIT 1 |
|
| 267 |
}); |
|
| 268 |
$pred_sth->execute($row->{device_path});
|
|
| 269 | ||
| 270 |
if (my $pred = $pred_sth->fetchrow_hashref()) {
|
|
| 271 |
$row->{latest_prediction} = $pred;
|
|
| 272 |
} |
|
| 273 | ||
| 274 |
# Get recent alerts |
|
| 275 |
my $alert_sth = $dbh->prepare(q{
|
|
| 276 |
SELECT COUNT(*) as alert_count |
|
| 277 |
FROM alert_history |
|
| 278 |
WHERE device_path = ? |
|
| 279 |
AND sent_at >= NOW() - INTERVAL ? DAY |
|
| 280 |
}); |
|
| 281 |
$alert_sth->execute($row->{device_path}, $days);
|
|
| 282 | ||
| 283 |
if (my $alert = $alert_sth->fetchrow_hashref()) {
|
|
| 284 |
$row->{recent_alert_count} = $alert->{alert_count};
|
|
| 285 |
} |
|
| 286 | ||
| 287 |
push @drives, $row; |
|
| 288 |
} |
|
| 289 | ||
| 290 |
return \@drives; |
|
| 291 |
} |
|
| 292 | ||
| 293 |
=head2 get_health_overview |
|
| 294 | ||
| 295 |
Get current health overview |
|
| 296 | ||
| 297 |
=cut |
|
| 298 | ||
| 299 |
sub get_health_overview {
|
|
| 300 |
my ($dbh, $device) = @_; |
|
| 301 | ||
| 302 |
my $sql = q{
|
|
| 303 |
SELECT * FROM drive_health_summary |
|
| 304 |
}; |
|
| 305 | ||
| 306 |
my @params = (); |
|
| 307 |
if ($device) {
|
|
| 308 |
$sql .= " WHERE device_path = ?"; |
|
| 309 |
push @params, $device; |
|
| 310 |
} |
|
| 311 | ||
| 312 |
$sql .= " ORDER BY device_path"; |
|
| 313 | ||
| 314 |
my $sth = $dbh->prepare($sql); |
|
| 315 |
$sth->execute(@params); |
|
| 316 | ||
| 317 |
my @health_data = (); |
|
| 318 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 319 |
push @health_data, $row; |
|
| 320 |
} |
|
| 321 | ||
| 322 |
return \@health_data; |
|
| 323 |
} |
|
| 324 | ||
| 325 |
=head2 get_alert_history |
|
| 326 | ||
| 327 |
Get alert history |
|
| 328 | ||
| 329 |
=cut |
|
| 330 | ||
| 331 |
sub get_alert_history {
|
|
| 332 |
my ($dbh, $device, $days) = @_; |
|
| 333 | ||
| 334 |
my $sql = q{
|
|
| 335 |
SELECT |
|
| 336 |
ah.device_path, |
|
| 337 |
ah.alert_type, |
|
| 338 |
ah.risk_level, |
|
| 339 |
ah.message, |
|
| 340 |
ah.sent_at, |
|
| 341 |
ah.acknowledged, |
|
| 342 |
ah.acknowledged_by, |
|
| 343 |
hi.model_name |
|
| 344 |
FROM alert_history ah |
|
| 345 |
JOIN hdd_inventory hi ON ah.device_path = hi.device_path |
|
| 346 |
WHERE ah.sent_at >= NOW() - INTERVAL ? DAY |
|
| 347 |
}; |
|
| 348 | ||
| 349 |
my @params = ($days); |
|
| 350 | ||
| 351 |
if ($device) {
|
|
| 352 |
$sql .= " AND ah.device_path = ?"; |
|
| 353 |
push @params, $device; |
|
| 354 |
} |
|
| 355 | ||
| 356 |
$sql .= " ORDER BY ah.sent_at DESC"; |
|
| 357 | ||
| 358 |
my $sth = $dbh->prepare($sql); |
|
| 359 |
$sth->execute(@params); |
|
| 360 | ||
| 361 |
my @alerts = (); |
|
| 362 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 363 |
push @alerts, $row; |
|
| 364 |
} |
|
| 365 | ||
| 366 |
return \@alerts; |
|
| 367 |
} |
|
| 368 | ||
| 369 |
=head2 get_trend_analysis |
|
| 370 | ||
| 371 |
Get trend analysis data |
|
| 372 | ||
| 373 |
=cut |
|
| 374 | ||
| 375 |
sub get_trend_analysis {
|
|
| 376 |
my ($dbh, $device, $days) = @_; |
|
| 377 | ||
| 378 |
# This is a simplified trend analysis |
|
| 379 |
# In production, you might want more sophisticated analysis |
|
| 380 | ||
| 381 |
my $sql = q{
|
|
| 382 |
SELECT |
|
| 383 |
device_path, |
|
| 384 |
DATE(timestamp) as date, |
|
| 385 |
AVG(temperature) as avg_temp, |
|
| 386 |
COUNT(*) as reading_count |
|
| 387 |
FROM smart_readings |
|
| 388 |
WHERE timestamp >= NOW() - INTERVAL ? DAY |
|
| 389 |
}; |
|
| 390 | ||
| 391 |
my @params = ($days); |
|
| 392 | ||
| 393 |
if ($device) {
|
|
| 394 |
$sql .= " AND device_path = ?"; |
|
| 395 |
push @params, $device; |
|
| 396 |
} |
|
| 397 | ||
| 398 |
$sql .= q{
|
|
| 399 |
GROUP BY device_path, DATE(timestamp) |
|
| 400 |
ORDER BY device_path, date |
|
| 401 |
}; |
|
| 402 | ||
| 403 |
my $sth = $dbh->prepare($sql); |
|
| 404 |
$sth->execute(@params); |
|
| 405 | ||
| 406 |
my %trends = (); |
|
| 407 |
while (my $row = $sth->fetchrow_hashref()) {
|
|
| 408 |
push @{$trends{$row->{device_path}}}, {
|
|
| 409 |
date => $row->{date},
|
|
| 410 |
avg_temp => sprintf("%.1f", $row->{avg_temp} || 0),
|
|
| 411 |
reading_count => $row->{reading_count},
|
|
| 412 |
}; |
|
| 413 |
} |
|
| 414 | ||
| 415 |
return \%trends; |
|
| 416 |
} |
|
| 417 | ||
| 418 |
=head2 output_text |
|
| 419 | ||
| 420 |
Output report as text |
|
| 421 | ||
| 422 |
=cut |
|
| 423 | ||
| 424 |
sub output_text {
|
|
| 425 |
my ($fh, $data, $type) = @_; |
|
| 426 | ||
| 427 |
print $fh "\n" . "="x80 . "\n"; |
|
| 428 |
print $fh "autoSMART System Report - " . ucfirst($type) . "\n"; |
|
| 429 |
print $fh "Generated: " . strftime("%Y-%m-%d %H:%M:%S", localtime($data->{generated_at})) . "\n";
|
|
| 430 |
print $fh "Time Period: Last $data->{days_included} days\n";
|
|
| 431 |
print $fh "="x80 . "\n\n"; |
|
| 432 | ||
| 433 |
if ($type eq 'summary') {
|
|
| 434 |
output_summary_text($fh, $data->{summary});
|
|
| 435 |
} elsif ($type eq 'detailed') {
|
|
| 436 |
output_detailed_text($fh, $data->{drives});
|
|
| 437 |
} elsif ($type eq 'health') {
|
|
| 438 |
output_health_text($fh, $data->{health});
|
|
| 439 |
} elsif ($type eq 'alerts') {
|
|
| 440 |
output_alerts_text($fh, $data->{alerts});
|
|
| 441 |
} elsif ($type eq 'trends') {
|
|
| 442 |
output_trends_text($fh, $data->{trends});
|
|
| 443 |
} |
|
| 444 |
} |
|
| 445 | ||
| 446 |
=head2 output_summary_text |
|
| 447 | ||
| 448 |
Output summary in text format |
|
| 449 | ||
| 450 |
=cut |
|
| 451 | ||
| 452 |
sub output_summary_text {
|
|
| 453 |
my ($fh, $summary) = @_; |
|
| 454 | ||
| 455 |
print $fh "SYSTEM OVERVIEW\n"; |
|
| 456 |
print $fh "-"x40 . "\n"; |
|
| 457 | ||
| 458 |
print $fh "Drive Status:\n"; |
|
| 459 |
foreach my $status (sort keys %{$summary->{drive_counts}}) {
|
|
| 460 |
printf $fh " %-10s: %d drives\n", ucfirst($status), $summary->{drive_counts}->{$status};
|
|
| 461 |
} |
|
| 462 | ||
| 463 |
if (%{$summary->{recent_predictions}}) {
|
|
| 464 |
print $fh "\nRecent Risk Predictions:\n"; |
|
| 465 |
foreach my $level (qw(critical high moderate low)) {
|
|
| 466 |
next unless $summary->{recent_predictions}->{$level};
|
|
| 467 |
printf $fh " %-10s: %d drives\n", ucfirst($level), $summary->{recent_predictions}->{$level};
|
|
| 468 |
} |
|
| 469 |
} |
|
| 470 | ||
| 471 |
if (%{$summary->{recent_alerts}}) {
|
|
| 472 |
print $fh "\nRecent Alerts:\n"; |
|
| 473 |
foreach my $type (sort keys %{$summary->{recent_alerts}}) {
|
|
| 474 |
printf $fh " %-15s: %d alerts\n", $type, $summary->{recent_alerts}->{$type};
|
|
| 475 |
} |
|
| 476 |
} |
|
| 477 | ||
| 478 |
if ($summary->{collection_stats}) {
|
|
| 479 |
my $stats = $summary->{collection_stats};
|
|
| 480 |
print $fh "\nData Collection:\n"; |
|
| 481 |
print $fh " Total readings: $stats->{total_readings}\n";
|
|
| 482 |
print $fh " Devices monitored: $stats->{devices_with_data}\n";
|
|
| 483 |
print $fh " Success rate: $stats->{success_rate}%\n";
|
|
| 484 |
} |
|
| 485 | ||
| 486 |
print $fh "\n"; |
|
| 487 |
} |
|
| 488 | ||
| 489 |
=head2 output_json |
|
| 490 | ||
| 491 |
Output report as JSON |
|
| 492 | ||
| 493 |
=cut |
|
| 494 | ||
| 495 |
sub output_json {
|
|
| 496 |
my ($fh, $data) = @_; |
|
| 497 | ||
| 498 |
my $json = JSON::XS->new->pretty->encode($data); |
|
| 499 |
print $fh $json; |
|
| 500 |
} |
|
| 501 | ||
| 502 |
=head2 output_html |
|
| 503 | ||
| 504 |
Output report as HTML (basic implementation) |
|
| 505 | ||
| 506 |
=cut |
|
| 507 | ||
| 508 |
sub output_html {
|
|
| 509 |
my ($fh, $data, $type) = @_; |
|
| 510 | ||
| 511 |
print $fh <<'EOF'; |
|
| 512 |
<!DOCTYPE html> |
|
| 513 |
<html> |
|
| 514 |
<head> |
|
| 515 |
<title>autoSMART Report</title> |
|
| 516 |
<style> |
|
| 517 |
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
| 518 |
table { border-collapse: collapse; width: 100%; }
|
|
| 519 |
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
| 520 |
th { background-color: #f2f2f2; }
|
|
| 521 |
.critical { color: #d32f2f; font-weight: bold; }
|
|
| 522 |
.high { color: #f57c00; font-weight: bold; }
|
|
| 523 |
.moderate { color: #fbc02d; }
|
|
| 524 |
.low { color: #388e3c; }
|
|
| 525 |
</style> |
|
| 526 |
</head> |
|
| 527 |
<body> |
|
| 528 |
EOF |
|
| 529 | ||
| 530 |
print $fh "<h1>autoSMART Report - " . ucfirst($type) . "</h1>\n"; |
|
| 531 |
print $fh "<p>Generated: " . strftime("%Y-%m-%d %H:%M:%S", localtime($data->{generated_at})) . "</p>\n";
|
|
| 532 | ||
| 533 |
# Basic HTML output - could be expanded significantly |
|
| 534 |
print $fh "<pre>" . encode_json($data) . "</pre>\n"; |
|
| 535 | ||
| 536 |
print $fh "</body></html>\n"; |
|
| 537 |
} |
|
| 538 | ||
| 539 |
# Additional text output functions would go here... |
|
| 540 |
sub output_detailed_text {
|
|
| 541 |
my ($fh, $drives) = @_; |
|
| 542 |
# Implementation for detailed drive output |
|
| 543 |
print $fh "DETAILED DRIVE INFORMATION\n"; |
|
| 544 |
print $fh "-"x40 . "\n"; |
|
| 545 |
foreach my $drive (@$drives) {
|
|
| 546 |
print $fh "Device: $drive->{device_path}\n";
|
|
| 547 |
print $fh "Model: " . ($drive->{model_name} || 'Unknown') . "\n";
|
|
| 548 |
print $fh "Serial: " . ($drive->{serial_number} || 'Unknown') . "\n";
|
|
| 549 |
print $fh "Status: $drive->{status}\n";
|
|
| 550 |
if ($drive->{latest_prediction}) {
|
|
| 551 |
print $fh "Latest Risk: $drive->{latest_prediction}->{risk_level}\n";
|
|
| 552 |
} |
|
| 553 |
print $fh "\n"; |
|
| 554 |
} |
|
| 555 |
} |
|
| 556 | ||
| 557 |
sub output_health_text {
|
|
| 558 |
my ($fh, $health) = @_; |
|
| 559 |
print $fh "DRIVE HEALTH OVERVIEW\n"; |
|
| 560 |
print $fh "-"x40 . "\n"; |
|
| 561 |
foreach my $drive (@$health) {
|
|
| 562 |
print $fh "$drive->{device_path}: $drive->{status}";
|
|
| 563 |
print $fh " (Risk: $drive->{risk_level})" if $drive->{risk_level};
|
|
| 564 |
print $fh "\n"; |
|
| 565 |
} |
|
| 566 |
} |
|
| 567 | ||
| 568 |
sub output_alerts_text {
|
|
| 569 |
my ($fh, $alerts) = @_; |
|
| 570 |
print $fh "ALERT HISTORY\n"; |
|
| 571 |
print $fh "-"x40 . "\n"; |
|
| 572 |
foreach my $alert (@$alerts) {
|
|
| 573 |
printf $fh "%s [%s] %s: %s\n", |
|
| 574 |
$alert->{sent_at},
|
|
| 575 |
$alert->{alert_type},
|
|
| 576 |
$alert->{device_path},
|
|
| 577 |
$alert->{message} || '';
|
|
| 578 |
} |
|
| 579 |
} |
|
| 580 | ||
| 581 |
sub output_trends_text {
|
|
| 582 |
my ($fh, $trends) = @_; |
|
| 583 |
print $fh "TREND ANALYSIS\n"; |
|
| 584 |
print $fh "-"x40 . "\n"; |
|
| 585 |
foreach my $device (sort keys %$trends) {
|
|
| 586 |
print $fh "Device: $device\n"; |
|
| 587 |
foreach my $trend (@{$trends->{$device}}) {
|
|
| 588 |
print $fh " $trend->{date}: Temp $trend->{avg_temp}°C ($trend->{reading_count} readings)\n";
|
|
| 589 |
} |
|
| 590 |
print $fh "\n"; |
|
| 591 |
} |
|
| 592 |
} |
|
| 593 | ||
| 594 |
=head2 print_help |
|
| 595 | ||
| 596 |
Display help information |
|
| 597 | ||
| 598 |
=cut |
|
| 599 | ||
| 600 |
sub print_help {
|
|
| 601 |
print <<'EOF'; |
|
| 602 |
autoSMART Report Generator v1.0 |
|
| 603 | ||
| 604 |
USAGE: |
|
| 605 |
autosmart-report.pl [OPTIONS] |
|
| 606 | ||
| 607 |
OPTIONS: |
|
| 608 |
--config-dir DIR Configuration directory (default: /etc/autosmart) |
|
| 609 |
--report TYPE Report type (default: summary) |
|
| 610 |
summary - System overview and statistics |
|
| 611 |
detailed - Detailed drive information |
|
| 612 |
health - Current health status of all drives |
|
| 613 |
alerts - Alert history |
|
| 614 |
trends - Trend analysis |
|
| 615 |
--device PATH Generate report for specific device only |
|
| 616 |
--days N Days of history to include (default: 30) |
|
| 617 |
--format FORMAT Output format: text, html, json (default: text) |
|
| 618 |
--output FILE Write to file instead of stdout |
|
| 619 |
--help Show this help message |
|
| 620 | ||
| 621 |
EXAMPLES: |
|
| 622 |
# System summary |
|
| 623 |
autosmart-report.pl --report summary |
|
| 624 | ||
| 625 |
# Detailed report for specific drive |
|
| 626 |
autosmart-report.pl --report detailed --device /dev/sda |
|
| 627 | ||
| 628 |
# Health status as HTML |
|
| 629 |
autosmart-report.pl --report health --format html --output health.html |
|
| 630 | ||
| 631 |
# Alert history for last week |
|
| 632 |
autosmart-report.pl --report alerts --days 7 |
|
| 633 | ||
| 634 |
# Trend analysis as JSON |
|
| 635 |
autosmart-report.pl --report trends --format json |
|
| 636 | ||
| 637 |
REPORT TYPES: |
|
| 638 |
summary High-level system statistics and overview |
|
| 639 |
detailed Comprehensive information about each drive |
|
| 640 |
health Current health status summary |
|
| 641 |
alerts Recent alerts and notifications |
|
| 642 |
trends Temperature and performance trends |
|
| 643 | ||
| 644 |
OUTPUT FORMATS: |
|
| 645 |
text Human-readable text format (default) |
|
| 646 |
html HTML report with basic styling |
|
| 647 |
json Machine-readable JSON format |
|
| 648 | ||
| 649 |
EOF |
|
| 650 |
} |
|
| 651 | ||
| 652 |
__END__ |
|
| 653 | ||
| 654 |
=head1 AUTHOR |
|
| 655 | ||
| 656 |
AutoSMART Development Team |
|
| 657 | ||
| 658 |
=head1 LICENSE |
|
| 659 | ||
| 660 |
This software is part of the autoSMART project. |
|
| 661 | ||
| 662 |
=cut |