Skip to content

Commit

Permalink
auto-tune based on dyno size
Browse files Browse the repository at this point in the history
  • Loading branch information
dzuelke committed Jan 30, 2015
1 parent 1746ff0 commit a5a46d6
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 3 deletions.
30 changes: 30 additions & 0 deletions bin/heroku-php-apache2
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,36 @@ if [[ -n ${httpd_config:-} || ( ${httpd_config:="$HEROKU_APP_DIR/$COMPOSER_VENDO
fi
httpd_config=$(php_passthrough "$httpd_config")

if [[ -z ${WEB_CONCURRENCY:-} ]]; then
maxprocs=$(ulimit -u)
ram="512M"
if [[ -z ${DYNO:-} ]]; then
# not on a dyno
[[ $verbose ]] && echo "Assuming settings for 1X dyno..." >&2
elif [[ "$maxprocs" == "32768" ]]; then
echo "Optimizing settings for PX dyno..." >&2
ram="6G"
elif [[ "$maxprocs" == "512" ]]; then
echo "Optimizing settings for 2X dyno..." >&2
ram="1G"
else
if [[ "$maxprocs" == "256" ]]; then
echo "Optimizing settings for 1X dyno..." >&2
else
echo "Falling back to settings for 1X dyno..." >&2
fi
fi

# determine number of FPM processes to run
read WEB_CONCURRENCY php_memory_limit <<<$(php -c "$php_config" $(composer config vendor-dir)/heroku/heroku-buildpack-php/bin/util/autotune.php -y "$fpm_config" -t "$DOCUMENT_ROOT" "$ram")
[[ $WEB_CONCURRENCY -lt 1 ]] && WEB_CONCURRENCY=1
export WEB_CONCURRENCY

echo "Running ${WEB_CONCURRENCY} workers at ${php_memory_limit}B memory limit."
else
echo "Running ${WEB_CONCURRENCY} workers as per \$WEB_CONCURRENCY"
fi

# make a shared pipe; we'll write the name of the process that exits to it once that happens, and wait for that event below
# this particular call works on Linux and Mac OS (will create a literal ".XXXXXX" on Mac, but that doesn't matter).
wait_pipe=$(mktemp -t "heroku.waitpipe-$PORT.XXXXXX" -u)
Expand Down
30 changes: 30 additions & 0 deletions bin/heroku-php-nginx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,36 @@ if [[ -n ${nginx_config:-} || ( ${nginx_config:="$HEROKU_APP_DIR/$COMPOSER_VENDO
fi
nginx_config=$(php_passthrough "$nginx_config")

if [[ -z ${WEB_CONCURRENCY:-} ]]; then
maxprocs=$(ulimit -u)
ram="512M"
if [[ -z ${DYNO:-} ]]; then
# not on a dyno
[[ $verbose ]] && echo "Assuming settings for 1X dyno..." >&2
elif [[ "$maxprocs" == "32768" ]]; then
echo "Optimizing settings for PX dyno..." >&2
ram="6G"
elif [[ "$maxprocs" == "512" ]]; then
echo "Optimizing settings for 2X dyno..." >&2
ram="1G"
else
if [[ "$maxprocs" == "256" ]]; then
echo "Optimizing settings for 1X dyno..." >&2
else
echo "Falling back to settings for 1X dyno..." >&2
fi
fi

# determine number of FPM processes to run
read WEB_CONCURRENCY php_memory_limit <<<$(php -c "$php_config" $(composer config vendor-dir)/heroku/heroku-buildpack-php/bin/util/autotune.php -y "$fpm_config" -t "$DOCUMENT_ROOT" "$ram")
[[ $WEB_CONCURRENCY -lt 1 ]] && WEB_CONCURRENCY=1
export WEB_CONCURRENCY

echo "Running ${WEB_CONCURRENCY} workers at ${php_memory_limit}B memory limit."
else
echo "Running ${WEB_CONCURRENCY} workers as per \$WEB_CONCURRENCY"
fi

# make a shared pipe; we'll write the name of the process that exits to it once that happens, and wait for that event below
# this particular call works on Linux and Mac OS (will create a literal ".XXXXXX" on Mac, but that doesn't matter).
wait_pipe=$(mktemp -t "heroku.waitpipe-$PORT.XXXXXX" -u)
Expand Down
80 changes: 80 additions & 0 deletions bin/util/autotune.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env php
<?php

function stringtobytes($amount) {
// convert "256M" etc to bytes
switch(strtolower(substr($amount, -1))) {
case 'g':
$amount = (int)$amount * 1024;
case 'm':
$amount = (int)$amount * 1024;
case 'k':
$amount = (int)$amount * 1024;
default:
$amount = (int)$amount;
}
return $amount;
}

// if given, parse FPM configs as well to figure out the memory limit
// (it may have been set in the FPM config or config include)
function get_fpm_memory_limit($fpmconf, $startsection = "global") {
if(!is_readable($fpmconf)) {
return array();
}
$fpm = parse_ini_string("[$startsection]\n".file_get_contents($fpmconf), true); // prepend section from parent so stuff is parsed correctly in includes that lack a leading section marker

$retval = array();

foreach($fpm as $section => $directives) {
foreach($directives as $key => $value) {
if($section == "www" && $key == "php_admin_value" && isset($value['memory_limit'])) {
$retval['php_admin_value'] = $value['memory_limit'];
} elseif($section == "www" && $key == "php_value" && isset($value['memory_limit']) && !isset($retval['php_value'])) {
// an existing value takes precedence
// we can only emulate that for includes; within the same file, the INI parser overwrites earlier values :(
$retval['php_value'] = $value['memory_limit'];
} elseif($key == "include") {
// values from the include don't overwrite existing values
$retval = array_merge(get_fpm_memory_limit($value, $section), $retval);
}

if(isset($retval['php_admin_value'])) {
// done for good as nothing can change this anymore, bubble upwards
return $retval;
}
}
}

return $retval;
}

$opts = getopt("y:t:");

$limits = get_fpm_memory_limit(isset($opts["y"]) ? $opts["y"] : null);

if(
!$limits /* .user.ini memory limit is ignored if one is set via FPM */ &&
isset($opts['t']) &&
is_readable($opts['t'].'/.user.ini')
) {
// we only read the topmost .user.ini inside document root
$userini = parse_ini_file($opts['t'].'/.user.ini');
if(isset($userini['memory_limit'])) {
$limits['php_value'] = $userini['memory_limit'];
}
}

if(isset($limits['php_admin_value'])) {
ini_set('memory_limit', $limits['php_admin_value']);
} elseif(isset($limits['php_value'])) {
ini_set('memory_limit', $limits['php_value']);
}

$limit = ini_get('memory_limit');
$ram = stringtobytes($argv[$argc-1]); // last arg is the available memory

// assume 64 MB base overhead for web server and FPM, and 1 MB overhead for each worker
echo floor(($ram-stringtobytes('64M'))/(stringtobytes($limit)+stringtobytes('1M'))) . " " . $limit;

?>
6 changes: 3 additions & 3 deletions conf/php/php-fpm.conf
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ listen = /tmp/heroku.fcgi.${PORT}.sock
; pm.process_idle_timeout - The number of seconds after which
; an idle process will be killed.
; Note: This value is mandatory.
pm = dynamic
pm = static

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
Expand All @@ -223,12 +223,12 @@ pm = dynamic
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 5
pm.max_children = ${WEB_CONCURRENCY}

; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 2
;pm.start_servers = 2

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
Expand Down

0 comments on commit a5a46d6

Please sign in to comment.