Advertisement
Aisberg

apachebuddy.pl

Mar 30th, 2017
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 34.70 KB | None | 0 0
  1. #!/usr/bin/perl
  2. #
  3. # author: jacob walcik
  4. # version: 0.3
  5. # description: This will make some recommendations on tuning your Apache
  6. # configuration based on your current settings and Apache's memory usage
  7. #
  8. # acknowledgements: This script was inspired by Major Hayden's MySQL Tuner
  9. # (http://mysqltuner.com).
  10.  
  11. use diagnostics;
  12. use Getopt::Long qw(:config no_ignore_case bundling pass_through);
  13. use POSIX;
  14. use strict;
  15. use Term::ANSIColor;
  16.  
  17. # here we're going to build a list of the files included by the Apache
  18. # configuration
  19. sub build_list_of_files {
  20.     my ($base_apache_config,$apache_root) = @_;
  21.  
  22.     # these to arrays will contain lists of Apache configuration files
  23.     # this is going to be the ultimate list of files that will be parsed
  24.     # searching for arguments
  25.     my @master_list;
  26.     # this will be a "scratch" space to store a list of files that
  27.     # currently need to be searched for more "include" lines
  28.     my @find_includes_in;
  29.  
  30.     # put the main configuration file into the list of files we're going
  31.     # to include
  32.     push(@master_list,$base_apache_config);
  33.  
  34.     # put the main configuratino file into the list of files we need to
  35.     # search for include lines
  36.     push(@find_includes_in,$base_apache_config);
  37.  
  38.     #get the Include lines from the main apache config
  39.     @master_list = find_included_files(\@master_list,\@find_includes_in,$apache_root);
  40. }
  41.  
  42. # here we're going to build an array holding the content of all of the
  43. # available configuration files
  44. sub build_config_array {
  45.     my ($base_apache_config,$apache_root) = @_;
  46.  
  47.     # these to arrays will contain lists of Apache configuration files
  48.     # this is going to be the ultimate list of files that will be parsed
  49.     # searching for arguments
  50.     my @master_list;
  51.  
  52.     # this will be a "scratch" space to store a list of files that
  53.     # currently need to be searched for more "include" lines
  54.     my @find_includes_in;
  55.  
  56.     # put the main configuration file into the list of files we're going
  57.     # to include
  58.     push(@master_list,$base_apache_config);
  59.  
  60.     # put the main configuratino file into the list of files we need to
  61.     # search for include lines
  62.     push(@find_includes_in,$base_apache_config);
  63.  
  64.     #get the Include lines from the main apache config
  65.     @master_list = find_included_files(\@master_list,\@find_includes_in,$apache_root);
  66. }
  67.  
  68. # this will find all of the files that need to be included
  69. sub find_included_files {
  70.     my ($master_list, $find_includes_in, $apache_root) = @_;
  71.  
  72.     # get the number of elements in the array
  73.     my $count = @$find_includes_in;
  74.  
  75.     # this array will eventually hold the entire apache configuration
  76.     my @master_config_array;
  77.  
  78.     # while there are still entries in this array, keep processing
  79.     while ( $count > 0 ) {
  80.         my $file = $$find_includes_in[0];
  81.        
  82.         print "VERBOSE: Processing ".$file."\n" if $main::VERBOSE;
  83.  
  84.         if(-d $file && $file !~ /\*$/) {
  85.             print "VERBOSE: Adding glob to ".$file.", is a directory\n" if $main::VERBOSE;
  86.             $file .= "/" if($file !~ /\/$/);
  87.             $file .= "*";
  88.         }
  89.  
  90.         # open the file
  91.         open(FILE,$file) || die("Unable to open file: ".$file."\n");
  92.        
  93.         # push the file into an array
  94.         my @file = <FILE>;
  95.  
  96.         # put the file in the master configuration array
  97.         push(@master_config_array,@file);
  98.  
  99.         # close the file
  100.         close(FILE);
  101.  
  102.         # search the file for includes
  103.         foreach (@file) {
  104.  
  105.             # this will be used to store a list of any new include
  106.             # lines found
  107.             # my @new_includes;
  108.  
  109.             # if the line looks like an include, then we want to examine it
  110.             if ( $_ =~ m/^\s*include/i ) {
  111.                 # grab the included file name or file glob
  112.                 $_ =~ s/\s*include\s+(.+)\s*/$1/i;
  113.  
  114.                 # strip out any quoting
  115.                 $_ =~ s/['"]+//g;
  116.  
  117.                 # prepend the Apache root for files or
  118.                 # globs that are relative
  119.                 if ( $_ !~ m/^\// ) {
  120.                     $_ = $apache_root."/".$_;
  121.                 }
  122.  
  123.                 # check for file globbing
  124.  
  125.                 if(-d $_ && $_ !~ /\*$/) {
  126.                     print "VERBOSE: Adding glob to ".$_.", is a directory\n" if $main::VERBOSE;
  127.                     $_ .= "/" if($_ !~ /\/$/);
  128.                     $_ .= "*";
  129.                 }
  130.  
  131.                 if ( $_ =~ m/.*\*.*/ ) {
  132.                     my $glob = $_;
  133.                     my @include_files;
  134.                     chomp($glob);
  135.  
  136.                     # if the include is a file glob,
  137.                     # expand it and add the files
  138.                     # to the list
  139.                     my @new_includes = expand_included_files(\@include_files, $glob, $apache_root);
  140.                     push(@$master_list,@new_includes);
  141.                     push(@$find_includes_in,@new_includes);
  142.                 }
  143.                 else {
  144.                     # if it is not a glob, push the
  145.                     # line into the configuration
  146.                     # array
  147.                     push(@$master_list,$_);
  148.                     push(@$find_includes_in,$_);
  149.                 }
  150.             }
  151.         }
  152.         # trim the first entry off the array now that we have
  153.         # processed it
  154.         shift(@$find_includes_in);
  155.  
  156.         # get the new count of files left to look at
  157.         $count = @$find_includes_in;
  158.     }
  159.  
  160.     # return the config array with the included files attached
  161.     return @master_config_array;
  162. }
  163.  
  164. # this will expand a glob into a list of individual files
  165. sub expand_included_files {
  166.     my ($include_files, $glob, $apache_root) = @_;
  167.  
  168.     # use a call to ls to get a list of the files from the glob
  169.     my @files = `ls $glob 2> /dev/null`;
  170.  
  171.     # add the files from the glob to the array we're going to pass back
  172.     foreach(@files) {
  173.         chomp($_);
  174.         push(@$include_files,$_);
  175.         print "VERBOSE: Adding ".$_." to list of files for processing\n" if $main::VERBOSE;
  176.     }
  177.  
  178.     # return the include_files array with the files from the glob attached
  179.     return @$include_files;
  180. }
  181.  
  182. # search the configuration array for a defined value that is not inside of a
  183. # virtual host
  184. sub find_master_value {
  185.     my ($config_array, $model, $config_element) = @_;
  186.  
  187.     # store our results in an array
  188.     my @results;
  189.  
  190.     # used to control whether or not we are currently ignoring elements
  191.     # while searching the array
  192.     my $ignore = 0;
  193.  
  194.     my $ignore_by_model = 0;
  195.     my $ifmodule_count = 0;
  196.  
  197.     # apache has two available models - prefork and worker. only one can be
  198.     # in use at a time. we have already determined which model is being
  199.     # used
  200.     my $ignore_model;
  201.    
  202.     if ( $model =~ m/.*worker.*/i ) {
  203.         $ignore_model = "prefork";
  204.     } else {
  205.         $ignore_model = "worker";
  206.     }
  207.  
  208.     print "VERBOSE: Searching Apache configuration for the ".$config_element." directive\n" if $main::VERBOSE;
  209.  
  210.     # search for the string in the configuration array
  211.     foreach (@$config_array) {
  212.         # ignore lines that are comments
  213.         if ( $_ !~ m/^\s*#/ ) {
  214.             chomp($_);
  215.  
  216.             # we ignore lines that are within a Directory, Location,
  217.             # File, or Virtualhost block
  218.        
  219.             # check to see if we have an opening tag for one of the
  220.             # block types listed above
  221.             if ( $_ =~ m/^\s*<(directory|location|files|virtualhost|ifmodule\s.*$ignore_model)/i ) {
  222.                 #print "Starting to ignore lines: ".$_."\n";
  223.                 $ignore = 1;
  224.             }
  225.             # check for a closing block to stop ignoring lines
  226.             if ( $_ =~ m/^\s*<\/(directory|location|files|virtualhost|ifmodule)/i ) {
  227.                 #print "Starting to watch lines: ".$_."\n";
  228.                 $ignore = 0;
  229.             }
  230.  
  231.             # if we're not ignoring lines, check and see if we've
  232.             # found the configuration element we're looking for
  233.             if ( $ignore != 1 ) {      
  234.                 # if we find a match
  235.                 if ( $_ =~ m/^\s*$config_element\s+.*/i ) {
  236.                     chomp($_);
  237.                     $_ =~ s/^\s*$config_element\s+(.*)/$1/i;
  238.                     push(@results,$_);
  239.                 }
  240.             }
  241.         }
  242.     }
  243.  
  244.     # if we find multiple definitions for the same element, we should
  245.     # return the last one
  246.     my $result;
  247.  
  248.     if ( @results > 1 ) {
  249.         $result = $results[@results - 1];
  250.     }
  251.     else {
  252.         $result = $results[0];
  253.     }
  254.  
  255.     #Result not found
  256.     if (@results == 0) {
  257.         $result = "CONFIG NOT FOUND";
  258.     }
  259.  
  260.     print "VERBOSE: $result " if $main::VERBOSE;
  261.     # Ubuntu does not store the Apache user, group, or pidfile definitions
  262.     # in the apache2.conf file. instead, variables are in the configuration
  263.     # file and the real values are in /etc/apache2/envvars. this is a
  264.     # workaround for that behavior.
  265.     if ( $config_element =~ m/[users|group|pidfile]/i && $result =~ m/^\$/i ) {
  266.         if ( -e "/etc/debian_version" && -e "/etc/apache2/envvars") {
  267.             print "VERBOSE: Using Ubuntu workaround for: ".$config_element."\n" if $main::VERBOSE;
  268.             print "VERBOSE: Processing /etc/apache2/envvars\n" if $main::VERBOSE;
  269.  
  270.             open(ENVVARS,"/etc/apache2/envvars") || die "Could not open file: /etc/apache2/envvars\n"; 
  271.             my @envvars = <ENVVARS>;
  272.             close(ENVVARS);
  273.  
  274.             # change "pidfile" to match Ubuntu's "pid_file"
  275.             # definition
  276.             if ( $config_element =~ m/pidfile/i ) {
  277.                 $config_element = "pid_file";
  278.             }
  279.  
  280.             foreach (@envvars) {
  281.                 if ( $_ =~ m/.*$config_element.*/i ) {
  282.                     chomp($_);
  283.                     $_ =~ s/^.*=(.*)\s*$/$1/i;
  284.                     $result = $_;
  285.                 }
  286.             }
  287.         }
  288.     }
  289.  
  290.     # return the value to the main program
  291.     return $result;
  292. }
  293.  
  294. # this will examine the memory usage of the apache processes and return one of
  295. # three different outputs: average usage across all processes, the memory usage
  296. # by the largest process, or the memory usage by the smallest process
  297. sub get_memory_usage {
  298.     my ($process_name, $apache_user, $search_type) = @_;
  299.  
  300.     my (@proc_mem_usages, $result);
  301.  
  302.     # get a list of the pid's for apache running as the appropriate user
  303.     my @pids = `ps aux | grep $process_name | grep -v root | grep $apache_user | awk \'{ print \$2 }\'`;
  304.  
  305.     # figure out how much memory each process is using
  306.     foreach (@pids) {
  307.         chomp($_);
  308.  
  309.         # pmap -d is used to determine the memory usage for the
  310.         # individual processes
  311.         my $pid_mem_usage = `pmap -d $_ | grep writeable/private | awk \'{ print \$4 }\'`;
  312.         $pid_mem_usage =~ s/K//;
  313.         chomp($pid_mem_usage);
  314.  
  315.         print "VERBOSE: Memory usage by PID ".$_." is ".$pid_mem_usage."K\n" if $main::VERBOSE;
  316.        
  317.         # on a busy system, the grep output will return the pid for the
  318.         # grep process itself, which will be gone by the time we get
  319.         # around to running pmap
  320.         if ( $pid_mem_usage ne "" ) {
  321.             push(@proc_mem_usages, $pid_mem_usage);
  322.         }
  323.     }
  324.  
  325.     # examine the array
  326.     if ( $search_type eq "high" ) {
  327.         # to find the largest process, sort the values from largest to
  328.         # smallest and take the first one
  329.         @proc_mem_usages = sort { $b <=> $a } @proc_mem_usages;
  330.         $result = $proc_mem_usages[0] / 1024;
  331.     }
  332.     if ( $search_type eq "low" ) {
  333.         # to find the smallest process, sort the values from smallest to
  334.         # largest and take the first one
  335.         @proc_mem_usages = sort { $a <=> $b } @proc_mem_usages;
  336.         $result = $proc_mem_usages[0] / 1024;
  337.     }
  338.     if ( $search_type eq "average" ) {
  339.         # to get the average, add up the total amount of memory used by
  340.         # each process, and then divide by the number of processes
  341.         my $sum = 0;
  342.         my $count;
  343.         foreach (@proc_mem_usages) {
  344.             $sum = $sum + $_;
  345.             $count++;
  346.         }
  347.  
  348.         # our result is in kilobytes, convert it to megabytes before
  349.         # returning it
  350.         $result = $sum / $count / 1024;
  351.     }
  352.    
  353.     # round off the result
  354.     $result = round($result);
  355.  
  356.     return $result;
  357. }
  358.  
  359. # this function accepts the path to a file and then tests to see whether the
  360. # item at that path is an Apache binary
  361. sub test_process {
  362.     my ($process_name) = @_;
  363.  
  364.         # Reduce to only aphanumerics, to deal with "nginx: master process" or any newlnes
  365.         $process_name = `echo -n $process_name | sed 's/://g'`;
  366.  
  367.     # the first line of output from "httpd -V" should tell us whether or
  368.     # not this is Apache
  369.     my @output = `$process_name -V 2>&1`;
  370.  
  371.     print "VERBOSE: First line of output from \"$process_name -V\": $output[0]" if $main::VERBOSE;
  372.  
  373.     my $return_val = 0;
  374.         #if ( $output eq '' ) {
  375.         #    $return_val = 0;
  376.         #}
  377.  
  378.     # check for output matching Apache'
  379.         if ( $output[0] =~ m/^Server version.*Apache\/[0-9].*/ ) {
  380.         $return_val = 1;
  381.     }
  382.  
  383.     return $return_val;
  384. }
  385.  
  386. # this will return the pid for the process listening on the port specified
  387. sub get_pid {
  388.     my ( $port ) = @_;
  389.  
  390.     # find the pid for the software listening on the specified port. this
  391.     # might return multiple values depending on Apache's listen directives
  392.     my @pids = `netstat -ntap | grep LISTEN | grep \":$port \" | awk \'{ print \$7 }\' | cut -d / -f 1`;
  393.  
  394.     print "VERBOSE: ".@pids." found listening on port 80\n" if $main::VERBOSE;
  395.  
  396.     # set an initial, invalid PID.
  397.     my $pid = 0;;
  398.     foreach (@pids) {
  399.         chomp($_);
  400.         $_ =~ s/(.*)\/.*/$1/;
  401.         if ( $pid == 0 ) {
  402.             $pid = $_;
  403.         }
  404.         elsif ( $pid != $_ ) {
  405.             print "There are multiple PIDs listening on port $port.";
  406.             exit;
  407.         }
  408.         else {
  409.             $pid = $_;
  410.         }
  411.     }
  412.  
  413.         # Fallback - Look for root owned httpd|apache2 process
  414.         #if ( $pid eq '' ) {
  415.         #   $pid = `ps aux | grep -v grep | egrep \'^root.*(httpd|apache2)\$\' | awk \'{print \$2}\'`;
  416.         #}
  417.  
  418.     # return the pid, or 0 if there is no process found
  419.     if ( $pid eq '' ) {
  420.         $pid = 0;
  421.     }
  422.  
  423.     print "VERBOSE: Returning a PID of ".$pid."\n" if $main::VERBOSE;
  424.  
  425.     return $pid;
  426. }
  427.  
  428. # this will return the path to the application running with the specified pid
  429. sub get_process_name {
  430.     my ( $pid ) = @_;
  431.  
  432.     print "VERBOSE: Finding process running with a PID of ".$pid."\n" if $main::VERBOSE;
  433.  
  434.     # based on the process name, we can figure out where the binary lives
  435.     my $process_name = `ps ax | grep "\^[[:space:]]*$pid\[[:space:]]" | awk \'{print \$5 }\'`;
  436.     chomp($process_name);
  437.  
  438.     print "VERBOSE: Found process ".$process_name."\n" if $main::VERBOSE;
  439.  
  440.     # return the process name, or 0 if there is no name found
  441.     if ( $process_name eq '' ) {
  442.         $process_name = 0;
  443.     }
  444.  
  445.     return $process_name;
  446. }
  447.  
  448. # this will return the apache root directory when given the full path to an
  449. # Apache binary
  450. sub get_apache_root {
  451.     my ( $process_name ) = @_;
  452.     # use the identified Apache binary to figure out where the root directory is
  453.     # for the Apache instance
  454.     my $apache_root = `$process_name -V | grep \"HTTPD_ROOT\"`;
  455.     $apache_root =~ s/.*=\"(.*)\"/$1/;
  456.     chomp($apache_root);
  457.  
  458.     if ( $apache_root eq '' ) {
  459.         $apache_root = 0;
  460.     }
  461.  
  462.     return $apache_root;
  463. }
  464.  
  465. # this will return the apache configuration file, relative to the apache root
  466. # for the provided apache binary
  467. sub get_apache_conf_file {
  468.     my ( $process_name ) = @_;
  469.     my $apache_conf_file = `$process_name -V | grep \"SERVER_CONFIG_FILE\"`;
  470.     $apache_conf_file =~ s/.*=\"(.*)\"/$1/;
  471.     chomp($apache_conf_file);
  472.  
  473.     # return the apache configuration file, or 0 if there is no result
  474.     if ( $apache_conf_file eq '' ) {
  475.         $apache_conf_file = 0;
  476.     }
  477.  
  478.     return $apache_conf_file;
  479. }
  480.  
  481. # this will determine whether this apache is using the worker or the prefork
  482. # model based on the way the binary was built
  483. sub get_apache_model {
  484.     my ( $process_name ) = @_;
  485.     my $model = `$process_name -l | egrep "worker.c|prefork.c"`;
  486.     chomp($model);
  487.     $model =~ s/\s*(.*)\.c/$1/;
  488.  
  489.     # return the name of the MPM, or 0 if there is no result
  490.     if ( $model eq '' ) {
  491.         $model = 0 ;
  492.     }
  493.  
  494.     return $model;
  495. }
  496.  
  497. # this will get the Apache version string
  498. sub get_apache_version {
  499.     my ( $process_name ) = @_;
  500.     my $version = `$process_name -V | grep "Server version"`;
  501.     chomp($version);
  502.     $version =~ s/.*:\s(.*)$/$1/;
  503.  
  504.     if ( $version eq '' ) {
  505.         $version = 0;
  506.     }
  507.  
  508.     return $version
  509. }
  510.  
  511. # this will us ps to determine the Apache uptime. it returns an array
  512. sub get_apache_uptime {
  513.     my ( $pid ) = @_;
  514.  
  515.     # this will return the running time for the given pid in the format
  516.     # "days-hours:minutes:seconds"
  517.     my $uptime = `ps -eo \"\%p \%t\" | grep $pid | grep -v grep | awk \'{ print \$2 }\'`;
  518.     chomp($uptime);
  519.  
  520.     print "VERBOSE: Raw uptime: $uptime\n" if $main::VERBOSE;
  521.  
  522.     # check to see if we've been running for multiple days
  523.     my ($days, $hours, $minutes, $seconds);
  524.     if ( $uptime =~ m/^.*-.*:.*:.*$/ ) {   
  525.         $days = $uptime;
  526.         $days =~ s/([0-9]*)-.*/$1/;
  527.  
  528.         # trim the days off of our uptime value
  529.         $uptime =~ s/.*-(.*)/$1/;
  530.    
  531.         ($hours, $minutes, $seconds) = split(':', $uptime);
  532.     }
  533.     elsif ( $uptime =~ m/^.*:.*:.*/ ) {
  534.         $days = 0;
  535.         ($hours, $minutes, $seconds) = split(':', $uptime);
  536.     }
  537.     elsif ( $uptime =~ m/^.*:.*/) {
  538.         $days = 0;
  539.         $hours = 0;
  540.         ($minutes, $seconds) = split(':', $uptime);
  541.     }
  542.     else {
  543.         $days = 0;
  544.         $hours = 0;
  545.         $minutes = 00;
  546.         $seconds = 00;
  547.     }
  548.  
  549.     # push everything into an array to pass back
  550.     my @apache_uptime = ( $days, $hours, $minutes, $seconds );
  551.  
  552.    
  553.  
  554.     return @apache_uptime;
  555. }
  556.  
  557. # return the global value for a PHP setting
  558. sub get_php_setting {
  559.     my ( $php_bin, $element ) = @_;
  560.  
  561.     # this will return an array with all of the local and global PHP
  562.     # settings
  563.     my @php_config_array = `php -r "phpinfo(4);"`;
  564.  
  565.     my @results;
  566.  
  567.     # search the array for our desired setting
  568.     foreach (@php_config_array) {
  569.         chomp($_);
  570.         if ( $_ =~ m/^\s*$element\s*/ ) {
  571.             chomp($_);
  572.             $_ =~ s/.*=>\s+(.*)\s+=>.*/$1/;
  573.             push(@results, $_);
  574.         }
  575.     }
  576.  
  577.         # if we find multiple definitions for the same element, we should
  578.         # return the last one (just in case)
  579.         my $result;
  580.  
  581.         if ( @results > 1 ) {
  582.                 $result = $results[@results - 1];
  583.         }
  584.         else {
  585.                 $result = $results[0];
  586.         }
  587.  
  588.     # some PHP directives are measured in MB. we want to trim the "M" off
  589.     # here for those that are
  590.     $result =~ s/^(.*)M$/$1/;
  591.  
  592.         # return the value to the main program
  593.         return $result;
  594. }
  595.  
  596. sub generate_standard_report {
  597.     my ( $available_mem, $maxclients, $apache_proc_highest, $model, $threadsperchild ) = @_;
  598.  
  599.  
  600.     # print a report header
  601.     print color 'bold white' if ! $main::NOCOLOR;
  602.     print "### GENERAL REPORT ###\n";
  603.     print color 'reset' if ! $main::NOCOLOR;
  604.  
  605.     # show what we're going to use to generate our numbers
  606.     print "\nSettings considered for this report:\n\n";
  607.  
  608.     print "\tYour server's physical RAM:\t\t";
  609.     print color 'bold' if ! $main::NOCOLOR;
  610.     print $available_mem."MB\n";
  611.     print color 'reset' if ! $main::NOCOLOR;
  612.  
  613.     print "\tApache's MaxClients directive:\t\t";
  614.     print color 'bold' if ! $main::NOCOLOR;
  615.     print $maxclients."\n";
  616.     print color 'reset' if ! $main::NOCOLOR;
  617.  
  618.     print "\tApache MPM Model:\t\t\t";
  619.     print color 'bold' if ! $main::NOCOLOR;
  620.     print $model ."\n";
  621.     print color 'reset' if ! $main::NOCOLOR;
  622.  
  623.     print "\tLargest Apache process (by memory):\t";
  624.     print color 'bold' if ! $main::NOCOLOR;
  625.     print $apache_proc_highest."MB\n";
  626.     print color 'reset' if ! $main::NOCOLOR;
  627.  
  628.     if ($model eq "prefork") {
  629.         # based on the Apache memory usage (size of the largest process,
  630.         # check to see if the maxclients setting for Apache is sane
  631.         my $max_rec_maxclients = $available_mem / $apache_proc_highest;
  632.         $max_rec_maxclients = floor($max_rec_maxclients);
  633.  
  634.         # determine the maximum potential memory usage by Apache
  635.         my $max_potential_usage = $maxclients * $apache_proc_highest;
  636.         my $max_potential_usage_pct = round(($max_potential_usage/$available_mem)*100);
  637.         if ( $maxclients <= $max_rec_maxclients ) {
  638.             print color 'bold green' if ! $main::NOCOLOR;
  639.             print "[ OK ]";
  640.             print color 'reset' if ! $main::NOCOLOR;
  641.             print "\tYour MaxClients setting is within an acceptable range.\n";
  642.             print "\tMax potential memory usage: \t\t";
  643.             print color 'bold white' if ! $main::NOCOLOR;
  644.             print $max_potential_usage." MB" ;
  645.             print color 'reset';
  646.             print "\n\n";
  647.  
  648.             print "\tPercentage of RAM allocated to Apache\t";
  649.             print color 'bold white' if ! $main::NOCOLOR;
  650.             print $max_potential_usage_pct." %" ;
  651.             print color 'reset';
  652.             print "\n\n";
  653.         }
  654.         else {
  655.             print color 'bold red' if ! $main::NOCOLOR;
  656.             print "[ !! ]";
  657.             print color 'reset' if ! $main::NOCOLOR;
  658.             print "\tYour MaxClients setting is too high. It should be no greater than ";
  659.             print color 'bold white' if ! $main::NOCOLOR;
  660.             print $max_rec_maxclients.".\n";
  661.             print color 'reset';
  662.             print "\tMax potential memory usage: ";
  663.             print color 'bold white' if ! $main::NOCOLOR;
  664.             print $max_potential_usage." MB" ."($max_potential_usage_pct % of available RAM)" ;
  665.             print color 'reset';
  666.             print "\n\n";
  667.  
  668.             print "\tPercentage of RAM allocated to Apache\t\t";
  669.             print color 'bold white' if ! $main::NOCOLOR;
  670.             print $max_potential_usage_pct." %" ;
  671.             print color 'reset';
  672.             print "\n\n";
  673.         }
  674.     }
  675.     if ($model eq "worker") {
  676.         my $max_rec_maxclients = int((($available_mem/$apache_proc_highest) * $threadsperchild)/25)*25;
  677.  
  678.         my $max_potential_usage = ($maxclients/$threadsperchild) * $apache_proc_highest;
  679.         $max_potential_usage = round($max_potential_usage);
  680.         my $max_potential_usage_pct = round(($max_potential_usage/$available_mem)*100);
  681.         if ( $maxclients <= $max_rec_maxclients ) {
  682.             print color 'bold green' if ! $main::NOCOLOR;
  683.             print "[ OK ]";
  684.             print color 'reset' if ! $main::NOCOLOR;
  685.             print "\tYour MaxClients setting is within an acceptable range.\n";
  686.             print "\t(Max potential memory usage: ";
  687.             print color 'bold white' if ! $main::NOCOLOR;
  688.             print $max_potential_usage." MB" ."($max_potential_usage_pct % of available RAM)" ;
  689.             print color 'reset';
  690.             print ")\n\n";
  691.            
  692.             print "\tPercentage of RAM allocated to Apache\t\t";
  693.             print color 'bold white' if ! $main::NOCOLOR;
  694.             print $max_potential_usage_pct." %" ;
  695.             print color 'reset';
  696.             print "\n\n";
  697.  
  698.         }
  699.         else {
  700.             print color 'bold red' if ! $main::NOCOLOR;
  701.             print "[ !! ]";
  702.             print color 'reset' if ! $main::NOCOLOR;
  703.             print "\tYour MaxClients setting is too high. It should be no greater than ";
  704.             print color 'bold white' if ! $main::NOCOLOR;
  705.             print $max_rec_maxclients.".\n";
  706.             print color 'reset';
  707.             print "\tMax potential memory usage: ";
  708.             print color 'bold white' if ! $main::NOCOLOR;
  709.             print $max_potential_usage." MB" ."($max_potential_usage_pct % of available RAM)" ;
  710.             print color 'reset';
  711.             print ")\n\n";
  712.  
  713.             print "\tPercentage of RAM allocated to Apache\t\t";
  714.             print color 'bold white' if ! $main::NOCOLOR;
  715.             print $max_potential_usage_pct." %" ;
  716.             print color 'reset';
  717.             print "\n\n";
  718.         }
  719.     }
  720.  
  721.     print "-----------------------------------------------------------------------\n";
  722. }
  723.  
  724. # generate the optional report based on the server's PHP settings
  725. sub generate_php_report {
  726.     my ( $available_mem, $maxclients ) = @_;
  727.  
  728.     # get the php memory_limit setting
  729.     my $apache_proc_php = get_php_setting('/usr/bin/php', 'memory_limit');
  730.  
  731.     # make a second recommendation based on potential PHP memory usage
  732.     my $max_rec_maxclients = $available_mem / $apache_proc_php;
  733.     $max_rec_maxclients = floor($max_rec_maxclients);
  734.  
  735.     # calculate the largest potential memory usage
  736.     my $max_potential_usage = $apache_proc_php * $maxclients;
  737.  
  738.     # print a report header
  739.     print color 'bold white' if ! $main::NOCOLOR;
  740.     print "### PHP REPORT ###\n";
  741.     print color 'reset' if ! $main::NOCOLOR;
  742.  
  743.     # show what we're going to use to generate our numbers
  744.     print "\nSettings considered for this report:\n\n";
  745.     print "\tYour server's physical RAM:\t\t";
  746.     print color 'bold' if ! $main::NOCOLOR;
  747.     print $available_mem."MB\n";
  748.     print color 'reset' if ! $main::NOCOLOR;
  749.     print "\tApache's MaxClients directive:\t\t";
  750.     print color 'bold' if ! $main::NOCOLOR;
  751.     print $maxclients."\n";
  752.     print color 'reset' if ! $main::NOCOLOR;
  753.     print "\tPHP's memory_limit setting:\t\t";
  754.     print color 'bold' if ! $main::NOCOLOR;
  755.     print $apache_proc_php."MB\n";
  756.     print color 'reset' if ! $main::NOCOLOR;
  757.     print "-----------------------------------------------------------------------\n";
  758.  
  759.     # see if the maxclients directive is below the calculated threshold
  760.     if ( $maxclients <= $max_rec_maxclients ) {
  761.         print color 'bold green' if ! $main::NOCOLOR;
  762.         print "[ OK ]";
  763.         print color 'reset' if ! $main::NOCOLOR;
  764.         print "\tYour MaxClients setting is within an acceptable range.\n";
  765.         print "\t(max potential memory usage by PHP under Apache: ";
  766.         print color 'bold white';
  767.         print $max_potential_usage."MB";
  768.         print color 'reset';
  769.         print ")\n\n";
  770.     }
  771.     else {
  772.         print color 'bold red' if ! $main::NOCOLOR;
  773.         print "[ !! ]";
  774.         print color 'reset' if ! $main::NOCOLOR;
  775.         print "\tYour MaxClients setting is too high. It should be no greater\n\tthan ";
  776.         print color 'bold white' if ! $main::NOCOLOR;
  777.         print $max_rec_maxclients.".\n";
  778.         print color 'reset';
  779.         print "\t(max potential memory usage by PHP under Apache: ";
  780.         print color 'bold white';
  781.         print $max_potential_usage."MB";
  782.         print color 'reset';
  783.         print ")\n\n";
  784.     }
  785. }
  786.  
  787. # this rounds a value to the nearest hundreth
  788. sub round {
  789.     my ( $value ) = @_;
  790.  
  791.     # add five thousandths
  792.     $value = $value + 0.005;
  793.  
  794.     # truncat the result
  795.     $value = sprintf("%.2f", $value);
  796.  
  797.     return $value;
  798. }  
  799.  
  800. #Return the number of CPU cores
  801. sub get_cores {
  802.     my $cmd = 'egrep ' . "'". '^physical id|^core id|^$' . "'" . ' /proc/cpuinfo | awk '. "'" . 'ORS=NR%3?",":"\n"' . "'" . '| sort | uniq | wc -l';
  803.     my $cmd_out = `$cmd`;
  804.     chomp $cmd_out;
  805.     return $cmd_out;
  806. }
  807.  
  808.  
  809. # print usage
  810. sub usage {
  811.     print "Usage: apachebuddy.pl [OPTIONS]\n";
  812.     print "If no options are specified, the basic tests will be run.\n";
  813.     print "\n";
  814.     print "\t-h, --help\tPrint this help message\n";
  815.     print "\t-p, --port=PORT\tSpecify an alternate port to check (default: 80)\n";
  816.     print "\t-P, --php\tInclude the PHP memory_limit setting when making the recommendation\n";
  817.     print "\t-v, --verbose\tUse verbose output (this is very noisy, only useful for debugging)\n";
  818.     print "\n";
  819. }
  820.  
  821. # print a header
  822. sub print_header {
  823.     print color 'bold white' if ! $main::NOCOLOR;
  824.     print "########################################################################\n";
  825.     print "# Apache Buddy v 0.3 ###################################################\n";
  826.     print "########################################################################\n";
  827.     print color 'reset' if ! $main::NOCOLOR;
  828. }
  829.  
  830. ########################
  831. # GATHER CMD LINE ARGS #
  832. ########################
  833.  
  834. # if help is not asked for, we do not give it
  835. my $help = "";
  836.  
  837. # if no port is specified, we default to 80
  838. my $port = 80;
  839.  
  840. # by default, do not include PHP in the check
  841. my $php = 0;
  842.  
  843. # by default, do not use verbose output
  844. our $VERBOSE = "";
  845.  
  846. # by default, use color output
  847. our $NOCOLOR = 0;
  848.  
  849. # grab the command line arguments
  850. GetOptions('help|h' => \$help, 'port|p:i' => \$port, 'php|P' => \$php, 'verbose|v' => \$VERBOSE, 'nocolor' => \$main::NOCOLOR);
  851.  
  852. # check for invalid options, bail if we find any and print the usage output
  853. if ( @ARGV > 0 ) {
  854.     print "Invalid option: ";
  855.     foreach (@ARGV) {
  856.         print $_." ";
  857.     }
  858.     print "\n";
  859.     usage;
  860.     exit;
  861. }
  862.  
  863. ########################
  864. # BEGIN MAIN EXECUTION #
  865. ########################
  866.  
  867. # make sure the script is being run as root
  868. my $uid = `id -u`;
  869. chomp($uid);
  870.  
  871. print "VERBOSE: UID of user is: ".$uid."\n" if $VERBOSE;
  872.  
  873. # we need to run as root to ensure that we can access all of the appropriate
  874. # files
  875. if ( $uid ne '0' ) {
  876.     print "This script must be run as root.\n";
  877.     exit;
  878. }
  879.  
  880. # this script uses pmap to determine the memory mapped to each apache
  881. # process. make sure that pmap is available.
  882. my $pmap = `which pmap`;
  883. chomp($pmap);
  884.  
  885. # make sure that pmap is available within our path
  886. if ( $pmap !~ m/.*\/pmap/ ) {
  887.     print "Unable to locate the pmap utility. This script requires pmap to analyze Apache's memory consumption.\n";
  888.     exit;
  889. }
  890.  
  891. # make sure PHP is available before we proceed
  892. if ( $php == 1 ) {
  893.     # check to see if there is a binary called "php" in our path
  894.     my $check = `which php`;
  895.  
  896.     if ( $check eq '' ) {
  897.         print "Unable to locate the PHP binary.\n";
  898.  
  899.         my $path = `echo \$PATH`;
  900.         chomp($path);
  901.         print "VERBOSE: Path: $path\n" if $VERBOSE;
  902.  
  903.         exit;
  904.     }
  905. }
  906.  
  907. # if the user has added the help flag, or if they have defined a port  
  908. if ( $help eq 1 || $port eq 0 ) {
  909.     usage();
  910.     exit;
  911. }
  912. elsif ( $port < 0 || $port > 65534 ) {
  913.     print "INVALID PORT: $port\n";
  914.     print "Valid port numbers are 1-65534\n";
  915.     exit;
  916. }
  917. else {
  918.     # print the header
  919.     print_header;
  920.  
  921.     print color 'bold white' if ! $NOCOLOR;
  922.     print "Gathering information...\n";
  923.     print color 'reset' if ! $NOCOLOR;
  924.  
  925.     # first thing we do is get the pid of the process listening on the
  926.     # specified port
  927.     print "We are checking the service running on port ".$port."\n";
  928.     my $pid = get_pid($port);
  929.  
  930.     print "VERBOSE: PID is ".$pid."\n" if $VERBOSE;
  931.  
  932.     if ( $pid eq 0 ) {
  933.         print "Unable to determine PID of the process.";
  934.         exit;
  935.     }
  936.  
  937.     # now we get the name of the process running with the specified pid
  938.     my $process_name = get_process_name($pid);
  939.     print "The process listening on port ".$port." is ".$process_name."\n";
  940.     if ( $process_name eq 0 ) {
  941.         print "Unable to determine the name of the process.";
  942.         exit;
  943.     }
  944.  
  945.     # check to see if there is a file in the file system at the path
  946.     # identified
  947.     #if  ( ! -e $process_name ) {
  948.     #   print "File .".$process_name." does not exist.\n";
  949.     #   exit;
  950.     #}
  951.  
  952.     # check to see if the process we have identified is Apache
  953.     my $is_it_apache = test_process($process_name);
  954.  
  955.     if ( $is_it_apache == 1 ) {
  956.         my $apache_version = get_apache_version($process_name);
  957.  
  958.         print "VERBOSE: Apache version: $apache_version\n" if $VERBOSE;
  959.  
  960.         # if we received a "0", just print "Apache"
  961.         if ( $apache_version eq 0 ) {
  962.             $apache_version = "Apache";
  963.         }
  964.  
  965.         print "The process running on port $port is $apache_version\n";
  966.     }
  967.     else {
  968.         print "The process running on port $port is not Apache. \n Falling back to process list...\n";
  969.                 $pid = `ps aux | grep -v grep | egrep \'^root\.\*(httpd|apache2)\$\' | awk \'BEGIN {ORS=\"\"} {print \$2}\' `;
  970.                 if ( $pid eq '' ) {
  971.                     print "Could not find Apache process. Exiting...\n";
  972.             exit;
  973.                 } else {
  974.                     # If we found it, then reset the proces_name
  975.                     $process_name = get_process_name($pid);
  976.                 }
  977.     }
  978.  
  979.     # determine the Apache uptime
  980.     my @apache_uptime = get_apache_uptime($pid);
  981.     print "Apache has been running ".$apache_uptime[0]."d ".$apache_uptime[1]."h ".$apache_uptime[2]."m ".$apache_uptime[3]."s\n";
  982.  
  983.     # find the apache root 
  984.     my $apache_root = get_apache_root($process_name);
  985.  
  986.     print "VERBOSE: The Apache root is: ".$apache_root."\n" if $VERBOSE;
  987.    
  988.     # find the apache configuration file (relative to the apache root)
  989.     my $apache_conf_file = get_apache_conf_file($process_name);
  990.     print "VERBOSE: The Apache config file is: ".$apache_conf_file."\n" if $VERBOSE;
  991.  
  992.     # piece together the full path to the configuration file, if a server
  993.     # does not have the HTTPD_ROOT value defined in its apache build, then
  994.     # try just using the path to the configuration file
  995.     my $full_apache_conf_file_path;
  996.     if ( -e $apache_conf_file ) {
  997.         $full_apache_conf_file_path = $apache_conf_file;
  998.         print "The full path to the Apache config file is: ".$full_apache_conf_file_path."\n";
  999.     }
  1000.     elsif ( -e $apache_root."/".$apache_conf_file ) {
  1001.         $full_apache_conf_file_path = $apache_root."/".$apache_conf_file;
  1002.         print "The full path to the Apache config file is: ".$full_apache_conf_file_path."\n";
  1003.     }
  1004.     else {
  1005.         print "Apache configuration file does not exist: ".$full_apache_conf_file_path."\n";
  1006.         exit;
  1007.     }
  1008.  
  1009.     # find out if we're using worker or prefork
  1010.     my $model = get_apache_model($process_name);   
  1011.     if ( $model eq 0 ) {
  1012.         print "Unable to determine whether Apache is using worker or prefork\n";
  1013.     }
  1014.     else {
  1015.         print "Apache is using $model model\n";
  1016.     }
  1017.  
  1018.     print color 'bold white' if ! $NOCOLOR;
  1019.     print "\nExamining your Apache configuration...\n";
  1020.     print color 'reset' if ! $NOCOLOR;
  1021.  
  1022.     # get the entire config, including included files, into an array
  1023.     my @config_array = build_config_array($full_apache_conf_file_path,$apache_root);
  1024.  
  1025.     # determine what user apache runs as
  1026.     my $apache_user = find_master_value(\@config_array, $model, 'user');
  1027.     if (length($apache_user) > 8) {
  1028.                 $apache_user = `id -u $apache_user`;
  1029.                 chomp($apache_user);
  1030.         }
  1031.     print "Apache runs as ".$apache_user."\n";
  1032.  
  1033.     # determine what the max clients setting is
  1034.     my $maxclients = find_master_value(\@config_array, $model, 'maxclients');
  1035.     $maxclients = 256 if($maxclients eq 'CONFIG NOT FOUND');
  1036.     print "Your max clients setting is ".$maxclients."\n";
  1037.  
  1038.     #calculate ThreadsPerChild. This is useful for the worker MPM calculations
  1039.         my $threadsperchild = find_master_value(\@config_array, $model, 'threadsperchild');
  1040.         my $serverlimit = find_master_value(\@config_array, $model, 'serverlimit');
  1041.  
  1042.     if ($model eq "worker") {
  1043.         print "Your ThreadsPerChild setting for worker MPM is  ".$threadsperchild."\n";
  1044.         print "Your ServerLimit setting for worker MPM is  ".$serverlimit."\n";
  1045.     }
  1046.  
  1047.     print color 'bold white' if ! $NOCOLOR;
  1048.     print "\nAnalyzing memory use...\n";
  1049.     print color 'reset' if ! $NOCOLOR;
  1050.  
  1051.     # figure out how much RAM is in the server
  1052.     my $available_mem = `free | grep \"Mem:\" | awk \'{ print \$2 }\'` / 1024;
  1053.     $available_mem = floor($available_mem);
  1054.  
  1055.     print "Your server has ".$available_mem." MB of memory\n";
  1056.  
  1057.     my $apache_proc_highest = get_memory_usage($process_name, $apache_user, 'high');
  1058.     my $apache_proc_lowest = get_memory_usage($process_name, $apache_user, 'low');
  1059.     my $apache_proc_average = get_memory_usage($process_name, $apache_user, 'average');
  1060.  
  1061.  
  1062.     if ( $model eq "prefork") {
  1063.         print "The largest apache process is using ".$apache_proc_highest." MB of memory\n";
  1064.         print "The smallest apache process is using ".$apache_proc_lowest." MB of memory\n";
  1065.         print "The average apache process is using ".$apache_proc_average." MB of memory\n";
  1066.  
  1067.         my $average_potential_use = $maxclients * $apache_proc_average;
  1068.         $average_potential_use = round($average_potential_use);
  1069.         my $average_potential_use_pct = round(($average_potential_use/$available_mem)*100);
  1070.         print "Going by the average Apache process, Apache can potentially use ".$average_potential_use." MB RAM ($average_potential_use_pct % of available RAM)\n" ;
  1071.  
  1072.         my $highest_potential_use = $maxclients * $apache_proc_highest;
  1073.         $highest_potential_use = round($highest_potential_use);
  1074.         my $highest_potential_use_pct = round(($highest_potential_use/$available_mem)*100);
  1075.         print "Going by the largest Apache process, Apache can potentially use ".$highest_potential_use." MB RAM ($highest_potential_use_pct % of available RAM)\n" ;
  1076.     }
  1077.  
  1078.     if ( $model eq "worker") {
  1079.         print "The largest apache process is using ".$apache_proc_highest." MB of memory\n";
  1080.         print "The smallest apache process is using ".$apache_proc_lowest." MB of memory\n";
  1081.         print "The average apache process is using ".$apache_proc_average." MB of memory\n";
  1082.  
  1083.         my $highest_potential_use = ($maxclients/$threadsperchild) * $apache_proc_highest;
  1084.         $highest_potential_use = round($highest_potential_use);
  1085.         my $highest_potential_use_pct = round(($highest_potential_use/$available_mem)*100);
  1086.         print "Going by the largest Apache process, Apache can potentially use ".$highest_potential_use." MB RAM ($highest_potential_use_pct % of available RAM)\n" ;
  1087.     }
  1088.     print color 'bold white' if ! $NOCOLOR;
  1089.     print "\nGenerating reports...\n";
  1090.     print color 'reset' if ! $NOCOLOR;
  1091.  
  1092.     # determine which report we're generating
  1093.     if ( $php == 1 ) {
  1094.         generate_php_report($available_mem, $maxclients);
  1095.     }
  1096.     else {
  1097.         generate_standard_report($available_mem, $maxclients, $apache_proc_highest, $model, $threadsperchild);
  1098.     }
  1099. }
  1100.  
  1101. print "-----------------------------------------------------------------------\n";
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement