Advertisement
bret_miller

memcron 0.6.3-Bret

Aug 12th, 2011
142
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 21.78 KB | None | 0 0
  1. #!/usr/bin/env perl
  2. our $VERSION = "0.6.3-Bret";
  3.  
  4. #////////////////////////////////////////////////////////////////////////////////////
  5. #
  6. #   memCron - DreamHost PS Memory Manager
  7. #
  8. #   Author     : Yaosan Yeo
  9. #   Version    : 0.6
  10. #   Date       : 22 March 2010
  11. #   URL        : http://memcron.com/
  12. #
  13. #   History  :
  14. #   0.6.2-chris [2010.06.30] - updated sendmail path from /usr/bin/sendmail to |/usr/sbin/sendmail
  15. #                                 |-> neet to place \ before @ in the email address (ex: me\@us.com)
  16. #                            + added random number to unique id
  17. #   0.6.1-ben [2010.03.22] - Added support for HTTP::MHTTP module
  18. #                          + Added a message log in ./logs/memcron.log, new config item $log_level
  19. #                (0 = no log, 3 = default, 5 = debugging)
  20. #   0.6 [2010.03.22] - Added: Various improvements contributed by memCron's user, Ben and Bret: (thanks guys!)
  21. #                      + Replaced most external calls with built-ins
  22. #                      + Removed dependency on Mail::SendMail
  23. #                      + Replaced LWP (slow and bloated) with system call to curl
  24. #                      + Get memory sizes from /proc/meminfo
  25. #                      + Simplified memory collecting
  26. #                      + Get current size from API rather than OS
  27. #                      + New options to disable updates, alerts
  28. #                      + Include mem target, cache used in csv
  29. #                      + Better error handling on memory resize
  30. #                    - Changed: Z-score function is now internal
  31. #
  32. #   0.4.1 [2009.08.10 ] - Fixed: Memory calculation due to the 2x increase of swap memory by DreamHost
  33. #
  34. #   0.4 [2009.06.28] - Added: New parameter "$max_memory" and "$min_memory" to limit the memory range that memCron should operate within
  35. #                    - Added: New parameter "$mem_threshold" to set a memory threshold, which when exceeded, will send out notification to $email
  36. #                    - Added: 4 new subroutines - round(), get_mem_size(), get_mem_size(), get_mem_target() to simplify the code structure
  37. #                    - Added: Base and swap memory size to function output
  38. #                    - Added: Resize information to function output
  39. #                    - Changed: "$debug" parameter moved from config.cfg to this file
  40. #                    - Removed: "$username" parameter, now DreamHost API only needs API key for authentication
  41. #                    - Fixed: Memory target are now computed more correctly during downsize
  42. #
  43. #   0.3 [2009.05.27] - Added: New parameter "$downsize_resistance" to prevent rapid fluctuations from causing multiple resizes within short period of time
  44. #                    - Changed: 0.3 to 0.7 ratio between "$mem_mean" and "$mem_used instead" of 0.5 to 0.5 when calculating "$mem_free_tolerance"
  45. #                    - Changed: More descriptive error message when log file cannot be opened
  46. #                    - Fixed: "$unix_time" and "$cpu_load" will now be reevaluated until it is defined to ensure data point is always logged (removed in 0.5)
  47. #                    - Fixed: Memory target calculations now correctly accounts max and swap memory, which is capped at 4000 MB and 450MB respectively
  48. #
  49. #   0.2 [2009.05.03] - Fixed: Account for "$num_data" in z-score calculation to ensure number of memory changes is minimized to within daily limits
  50. #                    - Changed: "$use_api" is changed to "$debug", which if set disables all log outputs and API calls
  51. #
  52. #   0.1 [2009.04.30] - Initial release
  53. #
  54. #////////////////////////////////////////////////////////////////////////////////////
  55. #
  56. # Copyright 2009 (c) Yaosan Yao - http://www.memcron.com/
  57. #
  58. # Portions of this product originally from psmanager:                     #
  59. # Copyright 2009 (C) Otto de Voogd - http://www.7is7.com/                 #
  60. #                                                                         #
  61. # This program is free software; you can redistribute it and/or modify    #
  62. # it under the terms of the GNU General Public License as published by    #
  63. # the Free Software Foundation; version 3 of the License.                 #
  64. #                                                                         #
  65. # This program is distributed in the hope that it will be useful,         #
  66. # but WITHOUT ANY WARRANTY; without even the implied warranty of          #
  67. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #
  68. # GNU General Public License for more details.                            #
  69. #                                                                         #
  70. # See license: http://www.gnu.org/licenses/gpl.html                       #
  71. #////////////////////////////////////////////////////////////////////////////////////
  72.  
  73. use strict;
  74. use warnings;
  75. no warnings 'uninitialized';
  76.  
  77. use File::Basename;
  78. use Time::Local;
  79. use Sys::Hostname;
  80.  
  81. #////////////////////////////////////////////////// get configurations
  82.  
  83. my $hostname = hostname;
  84. my $unix_time = time();
  85.  
  86.  
  87. # settings from config file
  88. our (
  89.     $key,                 $ps,                  $cron_interval,
  90.     $max_memory,          $min_memory,          $mem_threshold,
  91.     $disable_updates,     $alert_on_change,     $alert_on_error,
  92.     $email,               $mem_free_confidence, $mem_free_to_used_ratio,
  93.     $force_downsize_ratio,
  94.     $downsize_resistance, $num_data,            $num_data_csv,
  95.     $use_mhttp,           $debug,               $log_level
  96. );
  97.  
  98. $force_downsize_ratio=0.20;
  99.  
  100. my $dir = dirname($0);
  101. do "$dir/config.cfg";
  102.  
  103. $debug ||= $ARGV[0] eq '-d';
  104. if (!defined $log_level) { $log_level = $debug ? 5 : 3 }
  105.  
  106. ## try to use the MHTTP if it's available, set $use_mhttp=0 in config to avoid this.
  107. if ($use_mhttp || !defined($use_mhttp)) {
  108.     eval {
  109.         require "HTTP/MHTTP.pm";
  110.         import HTTP::MHTTP;
  111.     };
  112.     $use_mhttp = !$@;
  113. }
  114.  
  115. # Logs
  116. my $log_dir = "$dir/logs";
  117. my $log_csv = "$log_dir/$hostname.csv";
  118. my $log_memory = "$log_dir/$hostname.log";
  119. my $log_downsize = "$log_dir/$hostname-downsize.log";
  120. my $log_message = "$log_dir/memcron.log";
  121.  
  122. startup();
  123. local $SIG{__DIE__} = sub {
  124.     if ($^S) {
  125.         die @_;
  126.     } else {
  127.         FATAL("$_[0]");
  128.         cleanup(-1);
  129.     }
  130. };
  131.  
  132. # DreamHost PS limit
  133. my $daily_change_limit = 30;
  134. my $runs_per_day       = 1440 / $cron_interval;
  135.  
  136. # DreamHost PS defaults
  137. if ( $max_memory > 4000 ) { $max_memory = 4000; }
  138. if ( $min_memory < 300 )  { $min_memory = 300; }
  139.  
  140. # Statistics values
  141. my $SAMPLES = $debug ? 1 : 60;
  142. my $mem_used_zscore = abs( normsinv( ( $daily_change_limit * $num_data / $runs_per_day ) / $runs_per_day ) );    #1.89;
  143. my $mem_free_zscore = abs( normsinv( $mem_free_confidence ) );    #3.09;
  144.  
  145. #////////////////////////////////////////////////// main program starts
  146.  
  147. my ( $mem_total, $mem_used, $mem_free, $mem_cached, $cpu_load );
  148.  
  149. #my $list_ps = call_api('list_ps');
  150. #
  151. #if ( $list_ps->{status} ) {
  152. #   for (@{$list_ps->{data}}){
  153. #       next if $_->{ps} ne $hostname;
  154. #       $mem_total = $_->{memory_mb};
  155. #       last;
  156. #   }
  157. #   if (!$mem_total) {
  158. #       die "Dreamhost PS $hostname not found!";
  159. #   }
  160. #} else {
  161. #   die "$list_ps->{error}";
  162. #}
  163.  
  164. #my $mem = meminfo();
  165.  
  166. #foreach ( 1 .. $SAMPLES ) {
  167. #   $cpu_load += ( cpu_load() )[1];
  168. #   $mem = meminfo();
  169. #   $mem_used += $mem->{MemTotal} - $mem->{MemFree} + $mem->{SwapTotal} - $mem->{SwapFree};
  170. #   $mem_cached += $mem->{Cached};
  171. #   sleep 1 if !$debug;
  172. #}
  173. my $firstpass=1;
  174. foreach ( 1 .. $SAMPLES ) {
  175.     my $mu=0;
  176.     my $mc=0;
  177.     sleep 1 if !$firstpass;
  178.     $firstpass=0;
  179.     $cpu_load += ( cpu_load() )[1];
  180.     ($mem_total,$mu,$mem_free,$mc) = split /\t/, &getMemoryInfo($mem_total,$mu,$mem_free,$mc);
  181.     $mem_used+=$mu;
  182.     $mem_cached+=$mc;
  183. }
  184.  
  185. $mem_used = round( $mem_used / $SAMPLES );
  186. $mem_cached = round( $mem_cached / $SAMPLES );
  187. #$mem_used -= $mem_cached;
  188. $cpu_load = round( $cpu_load / $SAMPLES, 2 );
  189. $mem_free = $mem_total - $mem_used;
  190.  
  191. write_log( $log_memory, $mem_used, $num_data );
  192.  
  193.  
  194. #////////////// calculate the amount of memory to be reserved based on past data
  195.  
  196. my @data = read_log( $log_memory );
  197.  
  198. # construct a short list to be used when downsizing
  199. my @shortlist = @data[ ( scalar(@data) - $downsize_resistance ) .. ( scalar(@data) - 1 ) ];
  200.  
  201. # make sure we have enough data points before proceeding
  202. if ( scalar(@data) < round( $num_data * 0.1 ) ) {
  203.     WARN("need more data points");
  204.     cleanup();
  205. }
  206.  
  207. my ( $mem_stattotal, $mem_mean, $mem_median, $mem_stdev ) = stats(@data);
  208.  
  209. $mem_mean = round($mem_mean);
  210. $mem_stdev = round( $mem_stdev, 2 );
  211.  
  212. my $mem_used_tolerance = round( $mem_used_zscore * $mem_stdev );
  213. my $mem_free_tolerance = round( $mem_free_zscore * $mem_stdev );
  214.  
  215. unless ( $mem_free_tolerance > ( $mem_mean * 0.3 + $mem_used * 0.7 ) * $mem_free_to_used_ratio ) {
  216.     $mem_free_tolerance = round( ( $mem_mean * 0.3 + $mem_used * 0.7 ) * $mem_free_to_used_ratio );
  217. }
  218.  
  219. #////////////// calculate the memory required
  220.  
  221. my $mem_required = $mem_used + $mem_free_tolerance;
  222. my $mem_target   = get_mem_target($mem_required);
  223. my $set = ( abs( $mem_total - $mem_target ) > $mem_used_tolerance );
  224.  
  225. #////////////// resize resistance
  226. my $downsize_count = 0;
  227. if ( $set ) {
  228.     WARN("resize required $mem_target ($mem_total)");
  229.     if ( $mem_target < $mem_total ) {
  230.         $downsize_count = read_status( $log_downsize ) || 0;
  231.         $downsize_count++;
  232.  
  233.         if ( $downsize_count >= $downsize_resistance ) {
  234.             $downsize_count = 0;
  235.             $mem_required = round( ( stats(@shortlist) )[1] ) + $mem_free_tolerance;
  236.             $mem_target = get_mem_target($mem_required);
  237.         } else {
  238.             $set = 0;
  239.         }
  240.     }
  241. }
  242.  
  243. write_status( $log_downsize, $downsize_count );
  244.  
  245. #////////////// set memory using DreamHost API, output result
  246.  
  247. my $sendmail;
  248. my $subject;
  249. my $message = <<"MESSAGE";
  250. Total: $mem_total   Used: $mem_used Free: $mem_free
  251. Mean: $mem_mean Stdev: $mem_stdev   Load: $cpu_load
  252. Tolerance: $mem_used_tolerance  Tolerance (free): $mem_free_tolerance
  253.  
  254. Target: $mem_target
  255. MESSAGE
  256.  
  257. $set = 0 if $mem_target == $mem_total || $debug || $disable_updates;
  258.  
  259. if ($set) {
  260.     $message .= "\nMemory was resized from $mem_total MB to $mem_target MB!\n";
  261.  
  262.     # check against memory threshold, send notification email if it is exceeded
  263.     if ( $mem_threshold && $mem_target > $mem_threshold ) {
  264.         print "\n*** Warning! Threshold exceeded! ***\n";
  265.         $subject = "Memory threshold exceeded for $hostname";
  266.         $sendmail = 1 unless $debug;
  267.     }
  268.  
  269.     my $set_response;
  270.     my $with_force=0;
  271.     if ( ($force_downsize_ratio) && ($mem_target < $mem_total) && ((($mem_total-$mem_target)/$mem_total) >= $force_downsize_ratio)) {
  272.         #If we'll save more than 25%, then force the resize.
  273.         $set_response = call_api('set_size', {size => $mem_target,force => 'yes'});
  274.         $with_force=1;
  275.     } else {
  276.         $set_response = call_api('set_size', size => $mem_target);
  277.     }
  278.     if ( $set_response->{status} ) {
  279.         $subject = "Resized $hostname from $mem_total MB to $mem_target MB";
  280.         if ($with_force) { $subject.=", with force"; }
  281.         $sendmail = 1 if $alert_on_change;
  282.     } else {
  283.         $subject = "Unable to set memory size for $hostname";
  284.         $message .= $set_response->{error};
  285.         $sendmail = 1 if $alert_on_error;
  286.     }
  287.  
  288.     $message .= "\n";
  289.  
  290.     # send notification if necessary
  291.     if ( $sendmail && $email ) {
  292.         my $top = `top -b -n 1`;
  293.     chomp($top);
  294.         email_notice( $email, $subject, <<"MESSAGE" );
  295.  
  296. $message
  297. -----
  298.  
  299. List of running processes:
  300.  
  301. $top
  302.  
  303. MESSAGE
  304.  
  305.     } else {
  306.         $message .= "\nNo need to adjust memory" . ( ($downsize_count) ? " yet! ($downsize_count/$downsize_resistance)" : "!" ) . "\n";
  307.     }
  308. }
  309. print $message;
  310. print "\n-----\n";
  311.  
  312. #////////////// write memory usage and cpu load to csv file
  313.  
  314. my $log_line = join( ",",
  315.     $unix_time,          $mem_total,          $mem_used,
  316.     $cpu_load,           $mem_mean,           $mem_stdev,
  317.     $mem_used_tolerance, $mem_free_tolerance, $downsize_count,
  318.     $mem_target,         $mem_cached
  319. );
  320.  
  321. write_log($log_csv, $log_line, $num_data_csv);
  322. cleanup();
  323.  
  324.  
  325. #////////////// subroutines
  326.  
  327. sub cpu_load {
  328.     open( LOAD, "/proc/loadavg" ) or die "Unable to open /proc/loadavg";
  329.     my @l = split /\s+/, <LOAD>;
  330.     close(LOAD);
  331.     return @l;
  332. }
  333.  
  334. sub meminfo {
  335.     while (1) {
  336.         open( MEM, "/proc/meminfo" ) or die "Unable to open /proc/meminfo";
  337.         my %mem = map { (/(\w+):\s*(\d+)/) } <MEM>;
  338.         close(MEM);
  339.         ## kB to MB
  340.         for ( values %mem ) { $_ = int( $_ / 1024 ) }
  341.         ## sanity check MemTotal and retry if it's not valid
  342.         return \%mem if $mem{MemTotal} >= 300 && $mem{MemTotal} <= 4000;
  343.     }
  344. }
  345.  
  346. sub getMemoryInfo {
  347.     my ($tot_mem_avail,$tot_mem_used,$tot_mem_free,$tot_mem_cach) = @_;
  348.  
  349.     my $lines_read = 0;
  350.     my $free_output = `free -m`;
  351.  
  352.     my $mem_avail;
  353.     my $mem_used;
  354.     my $mem_free;
  355.     my $mem_cach;
  356.     my $swap_avail;
  357.     my $swap_used;
  358.     my $swap_free;
  359.  
  360.     foreach my $line (split /\n/, $free_output) {
  361.         $line =~ s/ +/ /g;
  362.         my @linepart = split / /, $line;
  363.         if ($line =~ /^Mem:/i) {
  364.             $lines_read++;
  365.             $mem_avail = $linepart[1];
  366.             $mem_cach = $linepart[5] + $linepart[6];
  367.             $mem_used = $linepart[2] - $mem_cach;
  368.             $mem_free = $linepart[3] + $mem_cach;
  369.         }
  370.         if ($line =~ /^Swap:/i) {
  371.             $lines_read++;
  372.             $swap_avail = $linepart[1];
  373.             $swap_used = $linepart[2];
  374.             $swap_free = $linepart[3];
  375.         }
  376.     }
  377.     if ($lines_read!=2) {
  378.         &printError(9,"Could not retrieve memory information!");
  379.         #die "Script terminated!\n";
  380.     }
  381.     # Occasionally excessively high memory values are returned
  382.     # by 'free', hence we check that the values make some sense.
  383.     if ( $mem_avail + $swap_avail < 16 * $max_memory ) {
  384.     $tot_mem_avail = $mem_avail + $swap_avail;
  385.         if (
  386.             ($mem_used + $swap_used >= 0) &&
  387.             ($mem_used + $swap_used <= $tot_mem_avail) &&
  388.             ($mem_used + $swap_used > $tot_mem_used)
  389.         ) {
  390.             $tot_mem_used = $mem_used + $swap_used;
  391.             $tot_mem_free = $mem_free + $swap_free;
  392.             $tot_mem_cach = $mem_cach;
  393.         }
  394.     }
  395.     return "$tot_mem_avail\t$tot_mem_used\t$tot_mem_free\t$tot_mem_cach";
  396. }
  397.  
  398. sub email_notice {
  399.     my ($to, $subject, $message) = @_;
  400.  
  401.     my $date = date_time_string();
  402.     open( MAIL, "|/usr/sbin/sendmail -t" ) or die "Unable to open sendmail: $!";
  403.     print MAIL <<"MESSAGE";
  404. To: $to
  405. From: memCron on $hostname <noreply\@memcron.com>
  406. Date: $date
  407. Subject: $subject
  408.  
  409. $message
  410.  
  411. -----------------------------------
  412. memCron $VERSION at $date
  413. -----------------------------------
  414. MESSAGE
  415.  
  416.     close(MAIL);
  417. }
  418.  
  419.  
  420. ## initialize message log, etc...
  421. sub startup {
  422.     if ( $log_level && !$debug ) {
  423.         open(LOGINFO, ">>$log_message") or die "Cannot open  system log $log_message!";
  424.     }
  425.     INFO("memcron $VERSION started (pid: $$)");
  426. }
  427.  
  428. sub cleanup {
  429.     INFO("memcron $VERSION complete");
  430.     if ( $log_level && !$debug ) {
  431.         close(LOGINFO);
  432.     }
  433.     exit $_[0]||0;
  434. }
  435.  
  436. sub log_message {
  437.     my $ts = '['.date_time_string(time).']';
  438.     my $indent;
  439.     for ( split/\n/, $_[0]) {
  440.         if ($debug) {
  441.             print "$ts $indent$_\n";
  442.         } else {
  443.             print LOGINFO "$ts $indent$_\n";
  444.         }
  445.         $indent = "|";
  446.     }
  447. }
  448.  
  449. sub FATAL { &log_message if $log_level >= 1 }
  450. sub ERROR { &log_message if $log_level >= 2 }
  451. sub WARN  { &log_message if $log_level >= 3 }
  452. sub INFO  { &log_message if $log_level >= 4 }
  453. sub DEBUG { &log_message if $log_level >= 5 }
  454.  
  455.  
  456.  
  457. sub write_log {
  458.     my ($file, $line, $truncate) = @_;
  459.     if ($debug) {
  460.         print "$file:\t$line\n";
  461.         return;
  462.     }
  463.     open(LOG, ">>$file") or die "Cannot open $file!";
  464.     print LOG $line,"\n";
  465.     close(LOG);
  466.     if ($truncate) {
  467.         truncate_log( $file, $truncate );
  468.     }
  469. }
  470.  
  471. sub read_log {
  472.     my ($file) = @_;
  473.     if ( ! -e $file ) { return () };
  474.     open(LOG, $file);
  475.     my @lines;
  476.     while (<LOG>) {
  477.         chomp;
  478.         push @lines, $_;
  479.     }
  480.     close LOG;
  481.     return @lines;
  482. }
  483.  
  484. sub truncate_log {
  485.     my ($file, $lines) = @_;
  486.     return unless -e $file;
  487.     system("tail -n $lines $file > $file.temp");
  488.     system("mv -f $file.temp $file");
  489. }
  490.  
  491. # single value status files
  492. sub write_status {
  493.     my ($file, $value) = @_;
  494.     if ($debug) {
  495.         print "$file:\t$value\n";
  496.         return;
  497.     }
  498.     open(LOG, ">$file") or die "Cannot open $file!";
  499.     print LOG $value,"\n";
  500.     close(LOG);
  501. }
  502.  
  503. sub read_status {
  504.     my ($file) = @_;
  505.     return unless -e $file;
  506.     open(LOG, $file);
  507.     my $value = <LOG>;
  508.     close LOG;
  509.     chomp $value;
  510.     return $value;
  511. }
  512.  
  513. # wraps call to DreamHost API so we can catch errors in a uniform way
  514. sub call_api {
  515.     my ( $command, %params ) = @_;
  516.     ## references globals: ($hostname, $time and $key)
  517.  
  518.     DEBUG("calling api $command");
  519.     my $url = 'https://panel.dreamhost.com/api/';
  520.     my %p = (
  521.         key       => $key,
  522.         cmd       => 'dreamhost_ps-' . $command,
  523.         unique_id => $unix_time . rand() . $$,
  524.         format    => 'perl',
  525.         ps        => $hostname,
  526.         %params
  527.     );
  528.     my $post = join(' ', map( "-d $_=$p{$_}", keys %p ) );
  529.     my $response = eval {
  530.         if ( $use_mhttp ) {
  531.             DEBUG("using mhttp");
  532.             $post = join('&', map("$_=$p{$_}", keys %p ));
  533.             http_set_protocol(1);
  534.             switch_debug(0);
  535.             http_add_headers(
  536.                     'User-Agent' => "memcron/$VERSION",
  537.                     'Connection' => "Keep-Alive",
  538.                     'Host' => 'panel.dreamhost.com',
  539.                     'Content-Type' => 'application/x-www-form-urlencoded',
  540.                 );
  541.             http_body($post);
  542.             if (http_call('POST', $url)) {
  543.                 if (http_status() == 200) {
  544.                     return http_response();
  545.                 } else {
  546.                     die  http_reason() . "\n";
  547.                 }
  548.             } else {
  549.                 die "Unable to connect to API\n"
  550.             }
  551.         } else {
  552.             DEBUG("using system curl");
  553.             $post = join(' ', map( "-d $_=$p{$_}", keys %p ) );
  554.             return `curl -s $post $url`;
  555.         }
  556.     }; 
  557.  
  558.     my ( $r, $error );
  559.     if ($@) {
  560.         $error = "Error: \n" . $@;
  561.     } elsif ( !$response ) {
  562.         $error = "No response\n";
  563.     } elsif ( $response !~ /^\$result/ ) {
  564.         $error = "Unknown response format:\n" . $response;
  565.     }
  566.  
  567.     my $result;
  568.     if ( !$error ) {
  569.         eval "$response";
  570.         if ( $result->{result} eq 'error' ) {
  571.             $error = "Error calling $command: $result->{data}\n";
  572.         } elsif ( $result->{result} ne 'success' ) {
  573.             $error = "Unknown API response:\n\n$response\n";
  574.         }
  575.     }
  576.  
  577.     return $error ? { status => 0, error => $error } : { status => 1, data => $result->{data} };
  578. }
  579.  
  580. sub round {
  581.     my ( $val, $places ) = @_;
  582.     $places ||= 0;
  583.     return sprintf( "%.${places}f", $val || 0 );
  584. }
  585.  
  586. sub get_mem_target {
  587.     my $mem_target = shift;
  588.  
  589.     # handle max and min memory limit
  590.     if ( $mem_target < $min_memory ) {
  591.         DEBUG("need $mem_target, using min_memory($min_memory)");
  592.         $mem_target = $min_memory;
  593.     } elsif ( $mem_target > $max_memory ) {
  594.         WARN("need $mem_target, using max_memory($max_memory)");
  595.         $mem_target = $max_memory;
  596.     }
  597.  
  598.     return ( round($mem_target) );
  599. }
  600.  
  601. sub stats {
  602.     my $total = 0;
  603.     foreach my $v (@_) { $total += $v; }
  604.  
  605.     my $average = $total / @_;
  606.     my $median = @_ % 2
  607.         ? $_[ ( @_ - 1 ) / 2 ]
  608.         : ( $_[ @_ / 2 - 1 ] + $_[ @_ / 2 ] ) / 2;
  609.  
  610.     my $sqtotal = 0;
  611.     foreach my $v (@_) {
  612.         $sqtotal += ( $average - $v )**2;
  613.     }
  614.     my $stdev = ( $sqtotal / @_ )**0.5;
  615.  
  616.     return ( $total, $average, $median, $stdev );
  617. }
  618.  
  619. sub date_time_string {
  620.     # convert a time() value to a date-time string according to RFC 822
  621.  
  622.     my $time = $_[0] || time();    # default to now if no argument
  623.  
  624.     my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
  625.     my @wdays  = qw(Sun Mon Tue Wed Thu Fri Sat);
  626.  
  627.     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime($time);
  628.  
  629.     my $TZ = $ENV{TZ};
  630.     if ( $TZ eq "" ) {
  631.         # offset in hours
  632.         my $offset = sprintf "%.1f", ( timegm(localtime) - time ) / 3600;
  633.         my $minutes = sprintf "%02d", abs( $offset - int($offset) ) * 60;
  634.         $TZ = sprintf( "%+03d", int($offset) ) . $minutes;
  635.     }
  636.  
  637.     return join( " ",
  638.         ( $wdays[$wday] . ',' ),
  639.         $mday, $months[$mon],
  640.         $year + 1900,
  641.         sprintf( "%02d:%02d:%02d", $hour, $min, $sec ), $TZ );
  642. }
  643.  
  644. sub normsinv {
  645.     #
  646.     # This function returns an approximation of the inverse cumulative
  647.     # standard normal distribution function.  I.e., given P, it returns
  648.     # an approximation to the X satisfying P = Pr{Z <= X} where Z is a
  649.     # random variable from the standard normal distribution.
  650.     #
  651.     # The algorithm uses a minimax approximation by rational functions
  652.     # and the result has a relative error whose absolute value is less
  653.     # than 1.15e-9.
  654.     #
  655.     # Author:      Peter J. Acklam
  656.     # Time-stamp:  2000-07-19 18:26:14
  657.     # E-mail:      pjacklam@online.no
  658.     # WWW URL:     http://home.online.no/~pjacklam
  659.  
  660.     my $p = shift;
  661.     die "input argument must be in (0,1)\n" unless 0 < $p && $p < 1;
  662.  
  663.     # Coefficients in rational approximations.
  664.     my @a = (-3.969683028665376e+01,  2.209460984245205e+02,
  665.              -2.759285104469687e+02,  1.383577518672690e+02,
  666.              -3.066479806614716e+01,  2.506628277459239e+00);
  667.     my @b = (-5.447609879822406e+01,  1.615858368580409e+02,
  668.              -1.556989798598866e+02,  6.680131188771972e+01,
  669.              -1.328068155288572e+01 );
  670.     my @c = (-7.784894002430293e-03, -3.223964580411365e-01,
  671.              -2.400758277161838e+00, -2.549732539343734e+00,
  672.               4.374664141464968e+00,  2.938163982698783e+00);
  673.     my @d = ( 7.784695709041462e-03,  3.224671290700398e-01,
  674.               2.445134137142996e+00,  3.754408661907416e+00);
  675.  
  676.     # Define break-points.
  677.     my $plow  = 0.02425;
  678.     my $phigh = 1 - $plow;
  679.  
  680.     # Rational approximation for lower region:
  681.     if ( $p < $plow ) {
  682.        my $q  = sqrt(-2*log($p));
  683.        return ((((($c[0]*$q+$c[1])*$q+$c[2])*$q+$c[3])*$q+$c[4])*$q+$c[5]) /
  684.                (((($d[0]*$q+$d[1])*$q+$d[2])*$q+$d[3])*$q+1);
  685.     }
  686.  
  687.     # Rational approximation for upper region:
  688.     if ( $phigh < $p ) {
  689.        my $q  = sqrt(-2*log(1-$p));
  690.        return -((((($c[0]*$q+$c[1])*$q+$c[2])*$q+$c[3])*$q+$c[4])*$q+$c[5]) /
  691.                 (((($d[0]*$q+$d[1])*$q+$d[2])*$q+$d[3])*$q+1);
  692.     }
  693.  
  694.     # Rational approximation for central region:
  695.     my $q = $p - 0.5;
  696.     my $r = $q*$q;
  697.     return ((((($a[0]*$r+$a[1])*$r+$a[2])*$r+$a[3])*$r+$a[4])*$r+$a[5])*$q /
  698.            ((((($b[0]*$r+$b[1])*$r+$b[2])*$r+$b[3])*$r+$b[4])*$r+1);
  699. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement