#######################################
APACHE TUNER SCRIPT
#####################################
#!/usr/bin/perl
#
# author: srinivas kanneboyina
# version: 0.3
# description: This will make some recommendations on tuning your Apache
# configuration based on your current settings and Apache's memory usage
#
# acknowledgements: This script was inspired by Major Hayden's MySQL Tuner
# (http://mysqltuner.com).
use diagnostics;
use Getopt::Long qw(:config no_ignore_case bundling pass_through);
use POSIX;
use strict;
use Term::ANSIColor;
# here we're going to build a list of the files included by the Apache
# configuration
sub build_list_of_files {
my ($base_apache_config,$apache_root) = @_;
# these to arrays will contain lists of Apache configuration files
# this is going to be the ultimate list of files that will be parsed
# searching for arguments
my @master_list;
# this will be a "scratch" space to store a list of files that
# currently need to be searched for more "include" lines
my @find_includes_in;
# put the main configuration file into the list of files we're going
# to include
push(@master_list,$base_apache_config);
# put the main configuratino file into the list of files we need to
# search for include lines
push(@find_includes_in,$base_apache_config);
#get the Include lines from the main apache config
@master_list = find_included_files(\@master_list,\@find_includes_in,$apache_root);
}
# here we're going to build an array holding the content of all of the
# available configuration files
sub build_config_array {
my ($base_apache_config,$apache_root) = @_;
# these to arrays will contain lists of Apache configuration files
# this is going to be the ultimate list of files that will be parsed
# searching for arguments
my @master_list;
# this will be a "scratch" space to store a list of files that
# currently need to be searched for more "include" lines
my @find_includes_in;
# put the main configuration file into the list of files we're going
# to include
push(@master_list,$base_apache_config);
# put the main configuratino file into the list of files we need to
# search for include lines
push(@find_includes_in,$base_apache_config);
#get the Include lines from the main apache config
@master_list = find_included_files(\@master_list,\@find_includes_in,$apache_root);
}
# this will find all of the files that need to be included
sub find_included_files {
my ($master_list, $find_includes_in, $apache_root) = @_;
# get the number of elements in the array
my $count = @$find_includes_in;
# this array will eventually hold the entire apache configuration
my @master_config_array;
# while there are still entries in this array, keep processing
while ( $count > 0 ) {
my $file = $$find_includes_in[0];
print "VERBOSE: Processing ".$file."\n" if $main::VERBOSE;
if(-d $file && $file !~ /\*$/) {
print "VERBOSE: Adding glob to ".$file.", is a directory\n" if $main::VERBOSE;
$file .= "/" if($file !~ /\/$/);
$file .= "*";
}
# open the file
open(FILE,$file) || die("Unable to open file: ".$file."\n");
# push the file into an array
my @file = <FILE>;
# put the file in the master configuration array
push(@master_config_array,@file);
# close the file
close(FILE);
# search the file for includes
foreach (@file) {
# this will be used to store a list of any new include
# lines found
# my @new_includes;
# if the line looks like an include, then we want to examine it
if ( $_ =~ m/^\s*include/i ) {
# grab the included file name or file glob
$_ =~ s/\s*include\s+(.+)\s*/$1/i;
# strip out any quoting
$_ =~ s/['"]+//g;
# prepend the Apache root for files or
# globs that are relative
if ( $_ !~ m/^\// ) {
$_ = $apache_root."/".$_;
}
# check for file globbing
if(-d $_ && $_ !~ /\*$/) {
print "VERBOSE: Adding glob to ".$_.", is a directory\n" if $main::VERBOSE;
$_ .= "/" if($_ !~ /\/$/);
$_ .= "*";
}
if ( $_ =~ m/.*\*.*/ ) {
my $glob = $_;
my @include_files;
chomp($glob);
# if the include is a file glob,
# expand it and add the files
# to the list
my @new_includes = expand_included_files(\@include_files, $glob, $apache_root);
push(@$master_list,@new_includes);
push(@$find_includes_in,@new_includes);
}
else {
# if it is not a glob, push the
# line into the configuration
# array
push(@$master_list,$_);
push(@$find_includes_in,$_);
}
}
}
# trim the first entry off the array now that we have
# processed it
shift(@$find_includes_in);
# get the new count of files left to look at
$count = @$find_includes_in;
}
# return the config array with the included files attached
return @master_config_array;
}
# this will expand a glob into a list of individual files
sub expand_included_files {
my ($include_files, $glob, $apache_root) = @_;
# use a call to ls to get a list of the files from the glob
my @files = `ls $glob 2> /dev/null`;
# add the files from the glob to the array we're going to pass back
foreach(@files) {
chomp($_);
push(@$include_files,$_);
print "VERBOSE: Adding ".$_." to list of files for processing\n" if $main::VERBOSE;
}
# return the include_files array with the files from the glob attached
return @$include_files;
}
# search the configuration array for a defined value that is not inside of a
# virtual host
sub find_master_value {
my ($config_array, $model, $config_element) = @_;
# store our results in an array
my @results;
# used to control whether or not we are currently ignoring elements
# while searching the array
my $ignore = 0;
my $ignore_by_model = 0;
my $ifmodule_count = 0;
# apache has two available models - prefork and worker. only one can be
# in use at a time. we have already determined which model is being
# used
my $ignore_model;
if ( $model =~ m/.*worker.*/i ) {
$ignore_model = "prefork";
} else {
$ignore_model = "worker";
}
print "VERBOSE: Searching Apache configuration for the ".$config_element." directive\n" if $main::VERBOSE;
# search for the string in the configuration array
foreach (@$config_array) {
# ignore lines that are comments
if ( $_ !~ m/^\s*#/ ) {
chomp($_);
# we ignore lines that are within a Directory, Location,
# File, or Virtualhost block
# check to see if we have an opening tag for one of the
# block types listed above
if ( $_ =~ m/^\s*<(directory|location|files|virtualhost|ifmodule\s.*$ignore_model)/i ) {
#print "Starting to ignore lines: ".$_."\n";
$ignore = 1;
}
# check for a closing block to stop ignoring lines
if ( $_ =~ m/^\s*<\/(directory|location|files|virtualhost|ifmodule)/i ) {
#print "Starting to watch lines: ".$_."\n";
$ignore = 0;
}
# if we're not ignoring lines, check and see if we've
# found the configuration element we're looking for
if ( $ignore != 1 ) {
# if we find a match
if ( $_ =~ m/^\s*$config_element\s+.*/i ) {
chomp($_);
$_ =~ s/^\s*$config_element\s+(.*)/$1/i;
push(@results,$_);
}
}
}
}
# if we find multiple definitions for the same element, we should
# return the last one
my $result;
if ( @results > 1 ) {
$result = $results[@results - 1];
}
else {
$result = $results[0];
}
#Result not found
if (@results == 0) {
$result = "CONFIG NOT FOUND";
}
print "VERBOSE: $result " if $main::VERBOSE;
# Ubuntu does not store the Apache user, group, or pidfile definitions
# in the apache2.conf file. instead, variables are in the configuration
# file and the real values are in /etc/apache2/envvars. this is a
# workaround for that behavior.
if ( $config_element =~ m/[users|group|pidfile]/i && $result =~ m/^\$/i ) {
if ( -e "/etc/debian_version" && -e "/etc/apache2/envvars") {
print "VERBOSE: Using Ubuntu workaround for: ".$config_element."\n" if $main::VERBOSE;
print "VERBOSE: Processing /etc/apache2/envvars\n" if $main::VERBOSE;
open(ENVVARS,"/etc/apache2/envvars") || die "Could not open file: /etc/apache2/envvars\n";
my @envvars = <ENVVARS>;
close(ENVVARS);
# change "pidfile" to match Ubuntu's "pid_file"
# definition
if ( $config_element =~ m/pidfile/i ) {
$config_element = "pid_file";
}
foreach (@envvars) {
if ( $_ =~ m/.*$config_element.*/i ) {
chomp($_);
$_ =~ s/^.*=(.*)\s*$/$1/i;
$result = $_;
}
}
}
}
# return the value to the main program
return $result;
}
# this will examine the memory usage of the apache processes and return one of
# three different outputs: average usage across all processes, the memory usage
# by the largest process, or the memory usage by the smallest process
sub get_memory_usage {
my ($process_name, $apache_user, $search_type) = @_;
my (@proc_mem_usages, $result);
# get a list of the pid's for apache running as the appropriate user
my @pids = `ps aux | grep $process_name | grep -v root | grep $apache_user | awk \'{ print \$2 }\'`;
# figure out how much memory each process is using
foreach (@pids) {
chomp($_);
# pmap -d is used to determine the memory usage for the
# individual processes
my $pid_mem_usage = `pmap -d $_ | grep writeable/private | awk \'{ print \$4 }\'`;
$pid_mem_usage =~ s/K//;
chomp($pid_mem_usage);
print "VERBOSE: Memory usage by PID ".$_." is ".$pid_mem_usage."K\n" if $main::VERBOSE;
# on a busy system, the grep output will return the pid for the
# grep process itself, which will be gone by the time we get
# around to running pmap
if ( $pid_mem_usage ne "" ) {
push(@proc_mem_usages, $pid_mem_usage);
}
}
# examine the array
if ( $search_type eq "high" ) {
# to find the largest process, sort the values from largest to
# smallest and take the first one
@proc_mem_usages = sort { $b <=> $a } @proc_mem_usages;
$result = $proc_mem_usages[0] / 1024;
}
if ( $search_type eq "low" ) {
# to find the smallest process, sort the values from smallest to
# largest and take the first one
@proc_mem_usages = sort { $a <=> $b } @proc_mem_usages;
$result = $proc_mem_usages[0] / 1024;
}
if ( $search_type eq "average" ) {
# to get the average, add up the total amount of memory used by
# each process, and then divide by the number of processes
my $sum = 0;
my $count;
foreach (@proc_mem_usages) {
$sum = $sum + $_;
$count++;
}
# our result is in kilobytes, convert it to megabytes before
# returning it
$result = $sum / $count / 1024;
}
# round off the result
$result = round($result);
return $result;
}
# this function accepts the path to a file and then tests to see whether the
# item at that path is an Apache binary
sub test_process {
my ($process_name) = @_;
# Reduce to only aphanumerics, to deal with "nginx: master process" or any newlnes
$process_name = `echo -n $process_name | sed 's/://g'`;
# the first line of output from "httpd -V" should tell us whether or
# not this is Apache
my @output = `$process_name -V 2>&1`;
print "VERBOSE: First line of output from \"$process_name -V\": $output[0]" if $main::VERBOSE;
my $return_val = 0;
#if ( $output eq '' ) {
# $return_val = 0;
#}
# check for output matching Apache'
if ( $output[0] =~ m/^Server version.*Apache\/[0-9].*/ ) {
$return_val = 1;
}
return $return_val;
}
# this will return the pid for the process listening on the port specified
sub get_pid {
my ( $port ) = @_;
# find the pid for the software listening on the specified port. this
# might return multiple values depending on Apache's listen directives
my @pids = `netstat -ntap | grep LISTEN | grep \":$port \" | awk \'{ print \$7 }\' | cut -d / -f 1`;
print "VERBOSE: ".@pids." found listening on port 80\n" if $main::VERBOSE;
# set an initial, invalid PID.
my $pid = 0;;
foreach (@pids) {
chomp($_);
$_ =~ s/(.*)\/.*/$1/;
if ( $pid == 0 ) {
$pid = $_;
}
elsif ( $pid != $_ ) {
print "There are multiple PIDs listening on port $port.";
exit;
}
else {
$pid = $_;
}
}
# Fallback - Look for root owned httpd|apache2 process
#if ( $pid eq '' ) {
# $pid = `ps aux | grep -v grep | egrep \'^root.*(httpd|apache2)\$\' | awk \'{print \$2}\'`;
#}
# return the pid, or 0 if there is no process found
if ( $pid eq '' ) {
$pid = 0;
}
print "VERBOSE: Returning a PID of ".$pid."\n" if $main::VERBOSE;
return $pid;
}
# this will return the path to the application running with the specified pid
sub get_process_name {
my ( $pid ) = @_;
print "VERBOSE: Finding process running with a PID of ".$pid."\n" if $main::VERBOSE;
# based on the process name, we can figure out where the binary lives
my $process_name = `ps ax | grep "\^[[:space:]]*$pid\[[:space:]]" | awk \'{print \$5 }\'`;
chomp($process_name);
print "VERBOSE: Found process ".$process_name."\n" if $main::VERBOSE;
# return the process name, or 0 if there is no name found
if ( $process_name eq '' ) {
$process_name = 0;
}
return $process_name;
}
# this will return the apache root directory when given the full path to an
# Apache binary
sub get_apache_root {
my ( $process_name ) = @_;
# use the identified Apache binary to figure out where the root directory is
# for the Apache instance
my $apache_root = `$process_name -V | grep \"HTTPD_ROOT\"`;
$apache_root =~ s/.*=\"(.*)\"/$1/;
chomp($apache_root);
if ( $apache_root eq '' ) {
$apache_root = 0;
}
return $apache_root;
}
# this will return the apache configuration file, relative to the apache root
# for the provided apache binary
sub get_apache_conf_file {
my ( $process_name ) = @_;
my $apache_conf_file = `$process_name -V | grep \"SERVER_CONFIG_FILE\"`;
$apache_conf_file =~ s/.*=\"(.*)\"/$1/;
chomp($apache_conf_file);
# return the apache configuration file, or 0 if there is no result
if ( $apache_conf_file eq '' ) {
$apache_conf_file = 0;
}
return $apache_conf_file;
}
# this will determine whether this apache is using the worker or the prefork
# model based on the way the binary was built
sub get_apache_model {
my ( $process_name ) = @_;
my $model = `$process_name -l | egrep "worker.c|prefork.c"`;
chomp($model);
$model =~ s/\s*(.*)\.c/$1/;
# return the name of the MPM, or 0 if there is no result
if ( $model eq '' ) {
$model = 0 ;
}
return $model;
}
# this will get the Apache version string
sub get_apache_version {
my ( $process_name ) = @_;
my $version = `$process_name -V | grep "Server version"`;
chomp($version);
$version =~ s/.*:\s(.*)$/$1/;
if ( $version eq '' ) {
$version = 0;
}
return $version
}
# this will us ps to determine the Apache uptime. it returns an array
sub get_apache_uptime {
my ( $pid ) = @_;
# this will return the running time for the given pid in the format
# "days-hours:minutes:seconds"
my $uptime = `ps -eo \"\%p \%t\" | grep $pid | grep -v grep | awk \'{ print \$2 }\'`;
chomp($uptime);
print "VERBOSE: Raw uptime: $uptime\n" if $main::VERBOSE;
# check to see if we've been running for multiple days
my ($days, $hours, $minutes, $seconds);
if ( $uptime =~ m/^.*-.*:.*:.*$/ ) {
$days = $uptime;
$days =~ s/([0-9]*)-.*/$1/;
# trim the days off of our uptime value
$uptime =~ s/.*-(.*)/$1/;
($hours, $minutes, $seconds) = split(':', $uptime);
}
elsif ( $uptime =~ m/^.*:.*:.*/ ) {
$days = 0;
($hours, $minutes, $seconds) = split(':', $uptime);
}
elsif ( $uptime =~ m/^.*:.*/) {
$days = 0;
$hours = 0;
($minutes, $seconds) = split(':', $uptime);
}
else {
$days = 0;
$hours = 0;
$minutes = 00;
$seconds = 00;
}
# push everything into an array to pass back
my @apache_uptime = ( $days, $hours, $minutes, $seconds );
return @apache_uptime;
}
# return the global value for a PHP setting
sub get_php_setting {
my ( $php_bin, $element ) = @_;
# this will return an array with all of the local and global PHP
# settings
my @php_config_array = `php -r "phpinfo(4);"`;
my @results;
# search the array for our desired setting
foreach (@php_config_array) {
chomp($_);
if ( $_ =~ m/^\s*$element\s*/ ) {
chomp($_);
$_ =~ s/.*=>\s+(.*)\s+=>.*/$1/;
push(@results, $_);
}
}
# if we find multiple definitions for the same element, we should
# return the last one (just in case)
my $result;
if ( @results > 1 ) {
$result = $results[@results - 1];
}
else {
$result = $results[0];
}
# some PHP directives are measured in MB. we want to trim the "M" off
# here for those that are
$result =~ s/^(.*)M$/$1/;
# return the value to the main program
return $result;
}
sub generate_standard_report {
my ( $available_mem, $maxclients, $apache_proc_highest, $model, $threadsperchild ) = @_;
# print a report header
print color 'bold white' if ! $main::NOCOLOR;
print "### GENERAL REPORT ###\n";
print color 'reset' if ! $main::NOCOLOR;
# show what we're going to use to generate our numbers
print "\nSettings considered for this report:\n\n";
print "\tYour server's physical RAM:\t\t";
print color 'bold' if ! $main::NOCOLOR;
print $available_mem."MB\n";
print color 'reset' if ! $main::NOCOLOR;
print "\tApache's MaxClients directive:\t\t";
print color 'bold' if ! $main::NOCOLOR;
print $maxclients."\n";
print color 'reset' if ! $main::NOCOLOR;
print "\tApache MPM Model:\t\t\t";
print color 'bold' if ! $main::NOCOLOR;
print $model ."\n";
print color 'reset' if ! $main::NOCOLOR;
print "\tLargest Apache process (by memory):\t";
print color 'bold' if ! $main::NOCOLOR;
print $apache_proc_highest."MB\n";
print color 'reset' if ! $main::NOCOLOR;
if ($model eq "prefork") {
# based on the Apache memory usage (size of the largest process,
# check to see if the maxclients setting for Apache is sane
my $max_rec_maxclients = $available_mem / $apache_proc_highest;
$max_rec_maxclients = floor($max_rec_maxclients);
# determine the maximum potential memory usage by Apache
my $max_potential_usage = $maxclients * $apache_proc_highest;
my $max_potential_usage_pct = round(($max_potential_usage/$available_mem)*100);
if ( $maxclients <= $max_rec_maxclients ) {
print color 'bold green' if ! $main::NOCOLOR;
print "[ OK ]";
print color 'reset' if ! $main::NOCOLOR;
print "\tYour MaxClients setting is within an acceptable range.\n";
print "\tMax potential memory usage: \t\t";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage." MB" ;
print color 'reset';
print "\n\n";
print "\tPercentage of RAM allocated to Apache\t";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage_pct." %" ;
print color 'reset';
print "\n\n";
}
else {
print color 'bold red' if ! $main::NOCOLOR;
print "[ !! ]";
print color 'reset' if ! $main::NOCOLOR;
print "\tYour MaxClients setting is too high. It should be no greater than ";
print color 'bold white' if ! $main::NOCOLOR;
print $max_rec_maxclients.".\n";
print color 'reset';
print "\tMax potential memory usage: ";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage." MB" ."($max_potential_usage_pct % of available RAM)" ;
print color 'reset';
print "\n\n";
print "\tPercentage of RAM allocated to Apache\t\t";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage_pct." %" ;
print color 'reset';
print "\n\n";
}
}
if ($model eq "worker") {
my $max_rec_maxclients = int((($available_mem/$apache_proc_highest) * $threadsperchild)/25)*25;
my $max_potential_usage = ($maxclients/$threadsperchild) * $apache_proc_highest;
$max_potential_usage = round($max_potential_usage);
my $max_potential_usage_pct = round(($max_potential_usage/$available_mem)*100);
if ( $maxclients <= $max_rec_maxclients ) {
print color 'bold green' if ! $main::NOCOLOR;
print "[ OK ]";
print color 'reset' if ! $main::NOCOLOR;
print "\tYour MaxClients setting is within an acceptable range.\n";
print "\t(Max potential memory usage: ";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage." MB" ."($max_potential_usage_pct % of available RAM)" ;
print color 'reset';
print ")\n\n";
print "\tPercentage of RAM allocated to Apache\t\t";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage_pct." %" ;
print color 'reset';
print "\n\n";
}
else {
print color 'bold red' if ! $main::NOCOLOR;
print "[ !! ]";
print color 'reset' if ! $main::NOCOLOR;
print "\tYour MaxClients setting is too high. It should be no greater than ";
print color 'bold white' if ! $main::NOCOLOR;
print $max_rec_maxclients.".\n";
print color 'reset';
print "\tMax potential memory usage: ";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage." MB" ."($max_potential_usage_pct % of available RAM)" ;
print color 'reset';
print ")\n\n";
print "\tPercentage of RAM allocated to Apache\t\t";
print color 'bold white' if ! $main::NOCOLOR;
print $max_potential_usage_pct." %" ;
print color 'reset';
print "\n\n";
}
}
print "-----------------------------------------------------------------------\n";
}
# generate the optional report based on the server's PHP settings
sub generate_php_report {
my ( $available_mem, $maxclients ) = @_;
# get the php memory_limit setting
my $apache_proc_php = get_php_setting('/usr/bin/php', 'memory_limit');
# make a second recommendation based on potential PHP memory usage
my $max_rec_maxclients = $available_mem / $apache_proc_php;
$max_rec_maxclients = floor($max_rec_maxclients);
# calculate the largest potential memory usage
my $max_potential_usage = $apache_proc_php * $maxclients;
# print a report header
print color 'bold white' if ! $main::NOCOLOR;
print "### PHP REPORT ###\n";
print color 'reset' if ! $main::NOCOLOR;
# show what we're going to use to generate our numbers
print "\nSettings considered for this report:\n\n";
print "\tYour server's physical RAM:\t\t";
print color 'bold' if ! $main::NOCOLOR;
print $available_mem."MB\n";
print color 'reset' if ! $main::NOCOLOR;
print "\tApache's MaxClients directive:\t\t";
print color 'bold' if ! $main::NOCOLOR;
print $maxclients."\n";
print color 'reset' if ! $main::NOCOLOR;
print "\tPHP's memory_limit setting:\t\t";
print color 'bold' if ! $main::NOCOLOR;
print $apache_proc_php."MB\n";
print color 'reset' if ! $main::NOCOLOR;
print "-----------------------------------------------------------------------\n";
# see if the maxclients directive is below the calculated threshold
if ( $maxclients <= $max_rec_maxclients ) {
print color 'bold green' if ! $main::NOCOLOR;
print "[ OK ]";
print color 'reset' if ! $main::NOCOLOR;
print "\tYour MaxClients setting is within an acceptable range.\n";
print "\t(max potential memory usage by PHP under Apache: ";
print color 'bold white';
print $max_potential_usage."MB";
print color 'reset';
print ")\n\n";
}
else {
print color 'bold red' if ! $main::NOCOLOR;
print "[ !! ]";
print color 'reset' if ! $main::NOCOLOR;
print "\tYour MaxClients setting is too high. It should be no greater\n\tthan ";
print color 'bold white' if ! $main::NOCOLOR;
print $max_rec_maxclients.".\n";
print color 'reset';
print "\t(max potential memory usage by PHP under Apache: ";
print color 'bold white';
print $max_potential_usage."MB";
print color 'reset';
print ")\n\n";
}
}
# this rounds a value to the nearest hundreth
sub round {
my ( $value ) = @_;
# add five thousandths
$value = $value + 0.005;
# truncat the result
$value = sprintf("%.2f", $value);
return $value;
}
#Return the number of CPU cores
sub get_cores {
my $cmd = 'egrep ' . "'". '^physical id|^core id|^$' . "'" . ' /proc/cpuinfo | awk '. "'" . 'ORS=NR%3?",":"\n"' . "'" . '| sort | uniq | wc -l';
my $cmd_out = `$cmd`;
chomp $cmd_out;
return $cmd_out;
}
# print usage
sub usage {
print "Usage: apachebuddy.pl [OPTIONS]\n";
print "If no options are specified, the basic tests will be run.\n";
print "\n";
print "\t-h, --help\tPrint this help message\n";
print "\t-p, --port=PORT\tSpecify an alternate port to check (default: 80)\n";
print "\t-P, --php\tInclude the PHP memory_limit setting when making the recommendation\n";
print "\t-v, --verbose\tUse verbose output (this is very noisy, only useful for debugging)\n";
print "\n";
}
# print a header
sub print_header {
print color 'bold white' if ! $main::NOCOLOR;
print "########################################################################\n";
print "# Apache Buddy v 0.3 ###################################################\n";
print "########################################################################\n";
print color 'reset' if ! $main::NOCOLOR;
}
########################
# GATHER CMD LINE ARGS #
########################
# if help is not asked for, we do not give it
my $help = "";
# if no port is specified, we default to 80
my $port = 80;
# by default, do not include PHP in the check
my $php = 0;
# by default, do not use verbose output
our $VERBOSE = "";
# by default, use color output
our $NOCOLOR = 0;
# grab the command line arguments
GetOptions('help|h' => \$help, 'port|p:i' => \$port, 'php|P' => \$php, 'verbose|v' => \$VERBOSE, 'nocolor' => \$main::NOCOLOR);
# check for invalid options, bail if we find any and print the usage output
if ( @ARGV > 0 ) {
print "Invalid option: ";
foreach (@ARGV) {
print $_." ";
}
print "\n";
usage;
exit;
}
########################
# BEGIN MAIN EXECUTION #
########################
# make sure the script is being run as root
my $uid = `id -u`;
chomp($uid);
print "VERBOSE: UID of user is: ".$uid."\n" if $VERBOSE;
# we need to run as root to ensure that we can access all of the appropriate
# files
if ( $uid ne '0' ) {
print "This script must be run as root.\n";
exit;
}
# this script uses pmap to determine the memory mapped to each apache
# process. make sure that pmap is available.
my $pmap = `which pmap`;
chomp($pmap);
# make sure that pmap is available within our path
if ( $pmap !~ m/.*\/pmap/ ) {
print "Unable to locate the pmap utility. This script requires pmap to analyze Apache's memory consumption.\n";
exit;
}
# make sure PHP is available before we proceed
if ( $php == 1 ) {
# check to see if there is a binary called "php" in our path
my $check = `which php`;
if ( $check eq '' ) {
print "Unable to locate the PHP binary.\n";
my $path = `echo \$PATH`;
chomp($path);
print "VERBOSE: Path: $path\n" if $VERBOSE;
exit;
}
}
# if the user has added the help flag, or if they have defined a port
if ( $help eq 1 || $port eq 0 ) {
usage();
exit;
}
elsif ( $port < 0 || $port > 65534 ) {
print "INVALID PORT: $port\n";
print "Valid port numbers are 1-65534\n";
exit;
}
else {
# print the header
print_header;
print color 'bold white' if ! $NOCOLOR;
print "Gathering information...\n";
print color 'reset' if ! $NOCOLOR;
# first thing we do is get the pid of the process listening on the
# specified port
print "We are checking the service running on port ".$port."\n";
my $pid = get_pid($port);
print "VERBOSE: PID is ".$pid."\n" if $VERBOSE;
if ( $pid eq 0 ) {
print "Unable to determine PID of the process.";
exit;
}
# now we get the name of the process running with the specified pid
my $process_name = get_process_name($pid);
print "The process listening on port ".$port." is ".$process_name."\n";
if ( $process_name eq 0 ) {
print "Unable to determine the name of the process.";
exit;
}
# check to see if there is a file in the file system at the path
# identified
#if ( ! -e $process_name ) {
# print "File .".$process_name." does not exist.\n";
# exit;
#}
# check to see if the process we have identified is Apache
my $is_it_apache = test_process($process_name);
if ( $is_it_apache == 1 ) {
my $apache_version = get_apache_version($process_name);
print "VERBOSE: Apache version: $apache_version\n" if $VERBOSE;
# if we received a "0", just print "Apache"
if ( $apache_version eq 0 ) {
$apache_version = "Apache";
}
print "The process running on port $port is $apache_version\n";
}
else {
print "The process running on port $port is not Apache. \n Falling back to process list...\n";
$pid = `ps aux | grep -v grep | egrep \'^root\.\*(httpd|apache2)\$\' | awk \'BEGIN {ORS=\"\"} {print \$2}\' `;
if ( $pid eq '' ) {
print "Could not find Apache process. Exiting...\n";
exit;
} else {
# If we found it, then reset the proces_name
$process_name = get_process_name($pid);
}
}
# determine the Apache uptime
my @apache_uptime = get_apache_uptime($pid);
print "Apache has been running ".$apache_uptime[0]."d ".$apache_uptime[1]."h ".$apache_uptime[2]."m ".$apache_uptime[3]."s\n";
# find the apache root
my $apache_root = get_apache_root($process_name);
print "VERBOSE: The Apache root is: ".$apache_root."\n" if $VERBOSE;
# find the apache configuration file (relative to the apache root)
my $apache_conf_file = get_apache_conf_file($process_name);
print "VERBOSE: The Apache config file is: ".$apache_conf_file."\n" if $VERBOSE;
# piece together the full path to the configuration file, if a server
# does not have the HTTPD_ROOT value defined in its apache build, then
# try just using the path to the configuration file
my $full_apache_conf_file_path;
if ( -e $apache_conf_file ) {
$full_apache_conf_file_path = $apache_conf_file;
print "The full path to the Apache config file is: ".$full_apache_conf_file_path."\n";
}
elsif ( -e $apache_root."/".$apache_conf_file ) {
$full_apache_conf_file_path = $apache_root."/".$apache_conf_file;
print "The full path to the Apache config file is: ".$full_apache_conf_file_path."\n";
}
else {
print "Apache configuration file does not exist: ".$full_apache_conf_file_path."\n";
exit;
}
# find out if we're using worker or prefork
my $model = get_apache_model($process_name);
if ( $model eq 0 ) {
print "Unable to determine whether Apache is using worker or prefork\n";
}
else {
print "Apache is using $model model\n";
}
print color 'bold white' if ! $NOCOLOR;
print "\nExamining your Apache configuration...\n";
print color 'reset' if ! $NOCOLOR;
# get the entire config, including included files, into an array
my @config_array = build_config_array($full_apache_conf_file_path,$apache_root);
# determine what user apache runs as
my $apache_user = find_master_value(\@config_array, $model, 'user');
if (length($apache_user) > 8) {
$apache_user = `id -u $apache_user`;
chomp($apache_user);
}
print "Apache runs as ".$apache_user."\n";
# determine what the max clients setting is
my $maxclients = find_master_value(\@config_array, $model, 'maxclients');
$maxclients = 256 if($maxclients eq 'CONFIG NOT FOUND');
print "Your max clients setting is ".$maxclients."\n";
#calculate ThreadsPerChild. This is useful for the worker MPM calculations
my $threadsperchild = find_master_value(\@config_array, $model, 'threadsperchild');
my $serverlimit = find_master_value(\@config_array, $model, 'serverlimit');
if ($model eq "worker") {
print "Your ThreadsPerChild setting for worker MPM is ".$threadsperchild."\n";
print "Your ServerLimit setting for worker MPM is ".$serverlimit."\n";
}
print color 'bold white' if ! $NOCOLOR;
print "\nAnalyzing memory use...\n";
print color 'reset' if ! $NOCOLOR;
# figure out how much RAM is in the server
my $available_mem = `free | grep \"Mem:\" | awk \'{ print \$2 }\'` / 1024;
$available_mem = floor($available_mem);
print "Your server has ".$available_mem." MB of memory\n";
my $apache_proc_highest = get_memory_usage($process_name, $apache_user, 'high');
my $apache_proc_lowest = get_memory_usage($process_name, $apache_user, 'low');
my $apache_proc_average = get_memory_usage($process_name, $apache_user, 'average');
if ( $model eq "prefork") {
print "The largest apache process is using ".$apache_proc_highest." MB of memory\n";
print "The smallest apache process is using ".$apache_proc_lowest." MB of memory\n";
print "The average apache process is using ".$apache_proc_average." MB of memory\n";
my $average_potential_use = $maxclients * $apache_proc_average;
$average_potential_use = round($average_potential_use);
my $average_potential_use_pct = round(($average_potential_use/$available_mem)*100);
print "Going by the average Apache process, Apache can potentially use ".$average_potential_use." MB RAM ($average_potential_use_pct % of available RAM)\n" ;
my $highest_potential_use = $maxclients * $apache_proc_highest;
$highest_potential_use = round($highest_potential_use);
my $highest_potential_use_pct = round(($highest_potential_use/$available_mem)*100);
print "Going by the largest Apache process, Apache can potentially use ".$highest_potential_use." MB RAM ($highest_potential_use_pct % of available RAM)\n" ;
}
if ( $model eq "worker") {
print "The largest apache process is using ".$apache_proc_highest." MB of memory\n";
print "The smallest apache process is using ".$apache_proc_lowest." MB of memory\n";
print "The average apache process is using ".$apache_proc_average." MB of memory\n";
my $highest_potential_use = ($maxclients/$threadsperchild) * $apache_proc_highest;
$highest_potential_use = round($highest_potential_use);
my $highest_potential_use_pct = round(($highest_potential_use/$available_mem)*100);
print "Going by the largest Apache process, Apache can potentially use ".$highest_potential_use." MB RAM ($highest_potential_use_pct % of available RAM)\n" ;
}
print color 'bold white' if ! $NOCOLOR;
print "\nGenerating reports...\n";
print color 'reset' if ! $NOCOLOR;
# determine which report we're generating
if ( $php == 1 ) {
generate_php_report($available_mem, $maxclients);
}
else {
generate_standard_report($available_mem, $maxclients, $apache_proc_highest, $model, $threadsperchild);
}
}
print "-----------------------------------------------------------------------\n";
No comments:
Post a Comment