#!/usr/bin/perl
# 
# This daemon monitors and logs preformance data. This is meant to help debug issues related to (potential)
# performance issues.
# 
# NOTE: This is designed to be minimal overhead, so there is no attempt to connect to the database. As such, 
#       be mindful of what this daemon is used for.
# 

use strict;
use warnings;
use Data::Dumper;
use Anvil::Tools;

my $THIS_FILE           =  ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory   =  ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
	$running_directory =~ s/^\./$ENV{PWD}/;
}

# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
$| = 1;

my $anvil = Anvil::Tools->new();

# Read switches
$anvil->Get->switches({list => [
	"all",
	"disable",
	"enable",
	"log-only",
	"monitor", 
	"now", 
	"start",
	"status", 
	"stop",
], man => $THIS_FILE});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});

# Common daemons
$anvil->data->{daemons}{core}{'anvil-daemon'}              = "anvil-daemon.service";
$anvil->data->{daemons}{core}{'scancore'}                  = "scancore.service";
$anvil->data->{daemons}{core}{'anvil-monitor-lvm'}         = "anvil-monitor-lvm.service";
$anvil->data->{daemons}{core}{'anvil-monitor-network'}     = "anvil-monitor-network.service";
$anvil->data->{daemons}{core}{'anvil-monitor-daemons'}     = "anvil-monitor-daemons.service";
$anvil->data->{daemons}{core}{'anvil-monitor-performance'} = "anvil-monitor-performance.service";

# Striker dashboards.
$anvil->data->{daemons}{striker}{'striker-ui-api'} = "striker-ui-api.service";

# Nodes and DR hosts
$anvil->data->{daemons}{node}{'anvil-safe-start'} = "anvil-safe-start.service";
$anvil->data->{daemons}{dr}{'anvil-safe-start'}   = "anvil-safe-start.service";

my $host_type = $anvil->Get->host_type();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { host_type => $host_type }});

my $daemon_list = [];
foreach my $daemon (sort {$a cmp $b} keys %{$anvil->data->{daemons}{core}})
{
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { daemon => $daemon }});
	push @{$daemon_list}, $daemon;
}
foreach my $daemon (sort {$a cmp $b} keys %{$anvil->data->{daemons}{$host_type}})
{
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { daemon => $daemon }});
	push @{$daemon_list}, $daemon;
}

if (($anvil->data->{switches}{monitor}) or ($anvil->data->{switches}{'log-only'}))
{
	# Run as a daemon
	monitor_daemons($anvil);
}
elsif ($anvil->data->{switches}{status})
{
	report_status($anvil, "stdout");
}
else
{
	# We're staritng, stopping, enabling or disabling. 
	if (($anvil->data->{switches}{enable})  or 
	    ($anvil->data->{switches}{disable}) or 
	    ($anvil->data->{switches}{start})   or 
	    ($anvil->data->{switches}{stop}))
	{
		check_daemon($anvil);
		process_daemons($anvil);
	}
	else
	{
		# Bad call
		print "Please see 'man ".$THIS_FILE." for usage.\n";
		$anvil->nice_exit({exit_code => 1});
	}
}

$anvil->nice_exit({exit_code => 0});


#############################################################################################################
# Functions                                                                                                 #
#############################################################################################################

sub report_status
{
	my ($anvil, $target) = @_;
	
	# Return Code meanings:
	# - Enabled = 0, Disabled = 1
	# - Started = 0, Stopped  = 3
	# - Failed  = 0, OK       = 1, 
	check_daemon($anvil);
	my $print = $target eq "stdout" ? 1 : 0;
	
	# Only print the header if we're printing to STDOUT
	if ($print)
	{
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => $print, level => 1, secure => 0, key => "message_0419"});
	}
	my $longest_daemon = $anvil->data->{longest_daemon};
	my $string = "";
	foreach my $daemon (sort {$a cmp $b} keys %{$anvil->data->{daemon}})
	{
		my $say_enabled = "unknown";
		my $say_started = "unknown";
		my $say_failed  = "unknown";
		
		# Enabled/Disabled
		if ($anvil->data->{daemon}{$daemon}{enabled}{return_code} == 0)
		{
			$say_enabled = "Enabled";
		}
		elsif ($anvil->data->{daemon}{$daemon}{enabled}{return_code} == 1)
		{
			$say_enabled = "Disabled";
		}
		
		# Started / Stopped
		if ($anvil->data->{daemon}{$daemon}{active}{return_code} == 0)
		{
			$say_started = "Started";
		}
		elsif ($anvil->data->{daemon}{$daemon}{active}{return_code} == 3)
		{
			$say_started = "Stopped";
		}
		
		# Failed / OK
		if ($anvil->data->{daemon}{$daemon}{failed}{return_code} == 0)
		{
			$say_failed = "Failed!";
		}
		elsif ($anvil->data->{daemon}{$daemon}{failed}{return_code} == 1)
		{
			$say_failed = "OK";
		}
		#my $say_daemon = sprintf("%-${longest_daemon}s", $daemon.";");
		my $say_daemon = $daemon."; ";
		my $dots = $longest_daemon - (length($daemon));
		for (my $i = 0; $i < $dots; $i++)
		{
			$say_daemon .= ".";
		}
		
		$string .= $daemon." [".$say_enabled."/".$say_started."/".$say_failed."], ";
		
		### NOTE: Set this to '3' because it was filling the logs when '--debug' is used.
		# Report.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => $print, level => 3, secure => 0, key => "message_0420", variables => { 
			daemon        => $say_daemon,
			enabled       => $say_enabled,
			enable_string => $anvil->data->{daemon}{$daemon}{enabled}{string},
			started       => $say_started, 
			start_string  => $anvil->data->{daemon}{$daemon}{active}{string}, 
			failed        => $say_failed, 
			fail_string   => $anvil->data->{daemon}{$daemon}{failed}{string}, 
		}});
	}
	
	$string =~ s/, $//gs;
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => $print, level => 1, secure => 0, key => "message_0421", variables => { string => $string }});
	
	return(0);
}

sub check_daemon
{
	my ($anvil) = @_;
	
	$anvil->data->{longest_daemon} = 0;
	foreach my $daemon (sort {$a cmp $b} @{$daemon_list})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { daemon => $daemon }});
		
		if (length($daemon) > $anvil->data->{longest_daemon})
		{
			$anvil->data->{longest_daemon} = length($daemon);
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { longest_daemon => $anvil->data->{longest_daemon} }});
		}
		
		# What's the status of this daemon?
		foreach my $test ("active", "enabled", "failed")
		{
			my $shell_call = $anvil->data->{path}{exe}{systemctl}." is-".$test." ".$daemon;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { shell_call => $shell_call }});
			
			my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
				output      => $output, 
			  	return_code => $return_code,
			}});
			
			# There should only be one line of output.
			$anvil->data->{daemon}{$daemon}{$test}{string}      = $output;
			$anvil->data->{daemon}{$daemon}{$test}{return_code} = $return_code;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
				"daemon::${daemon}::${test}::string"      => $anvil->data->{daemon}{$daemon}{$test}{string},
				"daemon::${daemon}::${test}::return_code" => $anvil->data->{daemon}{$daemon}{$test}{return_code},
			}});
		}
	}
	
	foreach my $daemon (sort {$a cmp $b} keys %{$anvil->data->{daemon}})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { daemon => $daemon }});
		foreach my $test (sort {$a cmp $b} keys %{$anvil->data->{daemon}{$daemon}})
		{
			my $string      = $anvil->data->{daemon}{$daemon}{$test}{string};
			my $return_code = $anvil->data->{daemon}{$daemon}{$test}{return_code};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
				"s1:test"        => $test, 
				"s2:string"      => $string, 
				"s3:return_code" => $return_code, 
			}});
		}
	}
	
	return(0);
}

sub process_daemons
{
	my ($anvil) = @_;
	
	# If enabling now, set the start switch, and inverse for disable
	if (($anvil->data->{switches}{enable}) && ($anvil->data->{switches}{now}) && (not $anvil->data->{switches}{start}))
	{
		print "Will start any stopped daemons ('--enable --now' used) after enabling daemons.\n";
		$anvil->data->{switches}{start} = 1;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { "switches::start" => $anvil->data->{switches}{start} }});
	}
	elsif (($anvil->data->{switches}{disable}) && ($anvil->data->{switches}{now}) && (not $anvil->data->{switches}{stop}))
	{
		print "Will stop any running daemons ('--disable --now' used) after disabling daemons.\n";
		$anvil->data->{switches}{stop} = 1;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { "switches::stop" => $anvil->data->{switches}{stop} }});
	}

	# Return Code meanings:
	# - Enabled = 0, Disabled = 1
	# - Started = 0, Stopped  = 3
	# - Failed  = 0, OK       = 1, 
	foreach my $daemon (sort {$a cmp $b} keys %{$anvil->data->{daemon}})
	{
		# Enable or disable?
		if (($anvil->data->{switches}{enable}) && ($anvil->data->{daemon}{$daemon}{enabled}{return_code} == 1))
		{
			# Is this optional, and if so, did they use '--all'?
			if (($daemon eq 'anvil-monitor-daemons') or ($daemon eq 'anvil-monitor-performance'))
			{
				if (not $anvil->data->{switches}{all})
				{
					# Skip this
					print "The daemon: [".$daemon."] is not critical, and '--all' not used, not enabling it.\n";
					next;
				}
			}
			
			my $shell_call = $anvil->data->{path}{exe}{systemctl}." enable ".$daemon;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { shell_call => $shell_call }});
			
			print "Enabling: [".$daemon."] now...\n";
			my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
				output      => $output, 
				return_code => $return_code,
			}});
		}
		elsif (($anvil->data->{switches}{disable}) && ($anvil->data->{daemon}{$daemon}{enabled}{return_code} == 0))
		{
			my $shell_call = $anvil->data->{path}{exe}{systemctl}." disable ".$daemon;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { shell_call => $shell_call }});
			
			print "Disabling: [".$daemon."] now...\n";
			my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
				output      => $output, 
				return_code => $return_code,
			}});
		}
		
		# Start or stop?
		if (($anvil->data->{switches}{start}) && ($anvil->data->{daemon}{$daemon}{active}{return_code} == 3))
		{
			if (($daemon eq 'anvil-monitor-daemons') or ($daemon eq 'anvil-monitor-performance'))
			{
				if (not $anvil->data->{switches}{all})
				{
					# Skip this
					print "The daemon: [".$daemon."] is not critical, and '--all' not used, not starting it.\n";
					next;
				}
			}
			
			my $shell_call = $anvil->data->{path}{exe}{systemctl}." start ".$daemon;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { shell_call => $shell_call }});
			
			print "Starting: [".$daemon."] now...\n";
			my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
				output      => $output, 
				return_code => $return_code,
			}});
		}
		elsif (($anvil->data->{switches}{stop}) && ($anvil->data->{daemon}{$daemon}{active}{return_code} == 0))
		{
			my $shell_call = $anvil->data->{path}{exe}{systemctl}." stop ".$daemon;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { shell_call => $shell_call }});
			
			print "Stopping: [".$daemon."] now...\n";
			my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
				output      => $output, 
				return_code => $return_code,
			}});
		}
	}
	print "Done.\n\n";
	
	sleep 1;
	report_status($anvil, "stdout");
	
	return(0);
}

sub monitor_daemons
{
	my ($anvil) = @_;

	# Calculate my sum so that we can exit if it changes later.
	$anvil->Storage->record_md5sums;
	my $next_md5sum_check = time + 30;
	
	while(1)
	{
		if (exists $anvil->data->{daemon})
		{
			delete $anvil->data->{daemon};
		}
		
		check_daemon($anvil);
		if ($anvil->data->{switches}{monitor})
		{
			foreach my $daemon (sort {$a cmp $b} keys %{$anvil->data->{daemon}})
			{
				# Return code of '1' is OK, '0' is failed.
				my $string      = $anvil->data->{daemon}{$daemon}{failed}{string};
				my $return_code = $anvil->data->{daemon}{$daemon}{failed}{return_code};
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
					"s1:daemon"      => $daemon,
					"s2:string"      => $string, 
					"s3:return_code" => $return_code, 
				}});
				
				if (not $return_code)
				{
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, secure => 0, key => "warning_0171", variables => { daemon => $daemon }});
					
					my $shell_call = $anvil->data->{path}{exe}{systemctl}." restart ".$daemon;
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { shell_call => $shell_call }});
					
					my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call});
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
						output      => $output, 
						return_code => $return_code,
					}});
				}
			}
		}
		else
		{
			report_status($anvil, "log");
		}
		
		if (time > $next_md5sum_check)
		{
			$next_md5sum_check = time + 30;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { next_md5sum_check => $next_md5sum_check }});
			if ($anvil->Storage->check_md5sums)
			{
				# NOTE: We exit with '0' to prevent systemctl from showing a scary red message.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "message_0014"});
				$anvil->nice_exit({exit_code => 0});
			}
		}
		sleep 60;
	}
	
	return(0);
}
