Apache チューニング スクリプト (MaxClientsを自動計算する)


# Apache の MaxClients の設定値を自動計算する
MySQL のチューニングをサポートするスクリプトとして MySQLTuner が有名だが、似たような発想で Apache の MaxClients (Apache 2.4 以降は MaxRequestWorkers) の設定値を自動計算するスクリプトがある。

**MaxClients の最適値**に関する議論は「[Apache の MaxClients の適正値](http://qiita.com/bezeklik/items/14e27417f9cc93ca7222)」に別途まとめたので参照されたい。

## 海外製
海外製で一番それっぽいのが **Apache2 Buddy** だが、子プロセスの共有メモリなどは考慮せずに単純計算をしているようだ。結果的に MaxClients は小さい数字になるので、無難に設定したい場合はこれを利用すればよい。と思ったが、Remaining Memory の計算が特定のサービスの除算しかしてないので、分母のメモリ量が多くなり大きい数字になってしまう場合があるので注意。

```text
        Your server's physical RAM:                                   993 MB
        Remaining Memory after other services considered:             993 MB
        Apache's MaxClients directive:                                256       <--------- Current Setting
        Apache MPM Model:                                             prefork
[ !! ]  Your MaxRequestWorkers setting is too high.
        Your recommended MaxRequestWorkers setting is between 168 and 187.      <------- Acceptable Range (10% of MAX)
        Max potential memory usage:                                   1354 MB
        Percentage of TOTAL RAM allocated to Apache:                  136.38  %
        Percentage of REMAINING RAM allocated to Apache:              136.38  %
```

## 子プロセスのメモリ出力
子プロセスのメモリ出力では、伊藤直也氏の記事で有名な shared_memory_size.pl の流れを汲んだ **none_shared_memory_fetcher.pl** が子プロセスの非共有メモリを計算してくれるが、 MaxClients の値は自分で計算する必要がある。

```text
PID     RSS     SHARED          NONE_SHARED
12538	3780	3072 (81%)      708
12540	3048	2836 (93%)      212
12541	3060	2840 (92%)      220
12542	3152	2908 (92%)      244
12543	3060	2840 (92%)      220
```

## 自動計算
**get_max_clients.sh** は、不完全ではあるものの、子プロセスの共有メモリを考慮した MaxClients の数値を自動計算してくれるので、ぎりぎりを攻めたいようであればこれを利用すると良いだろう。

```text
Memory Total / (Rss Average - Shr Average) = 1020076 / (3626 - 3263)
Memory Free  / (Rss Average - Shr Average) = 635560 / (3626 - 3263)
MaxClients = 1750 ~ 2810
```

# 海外製
## [Apachetuner.sh](https://github.com/gusmaskowitz/apachetuner)
Gustav Maskowitz ([@gusmaskowitz](https://github.com/gusmaskowitz)) によるコンセプトの記録としてのシェルスクリプト。当人による後述の Apachebuddy.pl に代替されている。

```bash:コマンド
curl -sL https://raw.githubusercontent.com/gusmaskowitz/apachetuner/master/apachetuner.sh | sh
```
```text:出力例
=========================SYSTEM=========================
CentOS release 6.8 (Final)

Server Name:                    localhost.localdomain
Total Physical Memory:          996 MB

=========================APACHE=========================
Version:                        Apache/2.2.15 (Unix)
RPM:                            httpd-2.2.15-53.el6.centos.x86_64
httpd binary:                   /usr/sbin/httpd
Whats running on port 80        12538/httpd
Apache Architecture:            64-bit
Serverlimit is:                 256
MaxClients is:                  256
httpd root                      /etc/httpd
httpd server config file        /etc/httpd/conf/httpd.conf
httpd default errorlog          /etc/httpd/logs/error_log

=========================PHP============================
/etc/php.ini memory_limit is:   128M

=====================APACHE RUNTIME=====================
Apache user:                    apache
Average Memory use:             2.07 MB per child
Number of children:             8

=========================REPORT==========================
Current memory footprint        16.56 MB
Maximum memory footprint        529.92 MB (53% of installed RAM)

System memory divided by MaxClients             4
System memory divided by Apache child size      481
```

## Apache Buddy
[Apache Buddy](https://github.com/gusmaskowitz/apachebuddy.pl) は Gustav Maskowitz (gusmaskowitz) により apachetuner.sh の代替として作成されたが、[ohaio-solo](https://github.com/rackerlabs/ohai-plugins/blob/master/plugins/apache2.rb) に同等の実装がされているのでアクティブな開発は行っていない、としている。

```bash:コマンド
curl -sL apachebuddy.pl | perl
```
```text:出力例
########################################################################
# Apache Buddy v 0.3 ###################################################
########################################################################
Gathering information...
We are checking the service running on port 80
The process listening on port 80 is /usr/sbin/httpd
The process running on port 80 is Apache/2.2.15 (Unix)
Apache has been running 0d 0h 18m 39s
The full path to the Apache config file is: /etc/httpd/conf/httpd.conf
Apache is using prefork model

Examining your Apache configuration...
Apache runs as apache
Your max clients setting is 256

Analyzing memory use...
Your server has 996 MB of memory
The largest apache process is using 2.08 MB of memory
The smallest apache process is using 2.08 MB of memory
The average apache process is using 2.08 MB of memory
Going by the average Apache process, Apache can potentially use 532.49 MB RAM (53.47 % of available RAM)
Going by the largest Apache process, Apache can potentially use 532.49 MB RAM (53.47 % of available RAM)

Generating reports...
### GENERAL REPORT ###

Settings considered for this report:

        Your server's physical RAM:             996MB
        Apache's MaxClients directive:          256
        Apache MPM Model:                       prefork
        Largest Apache process (by memory):     2.08MB
[ OK ]  Your MaxClients setting is within an acceptable range.
        Max potential memory usage:             532.48 MB

        Percentage of RAM allocated to Apache   53.47 %

-----------------------------------------------------------------------
-----------------------------------------------------------------------
```
### Henry Wong (somethingweird) フォーク
apachebuddy.pl に対する CentOS 7.0 - Apache 2.4.6 対応パッチ版

```
curl https://raw.githubusercontent.com/somethingweird/apachebuddy.pl/master/apachebuddy.pl | perl
```

## [Apache2 Buddy](https://github.com/richardforth/apache2buddy)
Richard Forth ([@richardforth](https://github.com/richardforth)) による Apache Buddy のフォーク。
物理メモリをApacheプロセスの平均メモリで割った値、およびその9割を **MaxClients** の許容範囲としていて、子プロセスの共有メモリなどは考慮していない模様。
`netstat` コマンドを使用しているため RHEL/CentOS 7.x では事前に `yum install net-tools` の実行が必要。

```bash:コマンド
curl -sL apache2buddy.pl -o apache2buddy.pl
perl apache2buddy.pl -r
```
```text:出力例
[ CRITICAL ] Going by the average Apache process, Apache can potentially use 1354.25 MB RAM:
                Without considering services: 136.38 % of total installed RAM
                Considering extra services: 136.38 % of remaining RAM
[ CRITICAL ] Going by the largest Apache process, Apache can potentially use 1354.25 MB RAM:
                Without considering services: 136.38 % of total installed RAM
                Considering extra services: 136.38 % of remaining RAM
--------------------------------------------------------------------------------
Apache2buddy.pl report for server: localhost (14.3.31.162):

Settings considered for this report:

        Your server's physical RAM:                                   993 MB
        Remaining Memory after other services considered:             993 MB
        Apache's MaxClients directive:                                256       <--------- Current Setting
        Apache MPM Model:                                             prefork
[ !! ] 	Your MaxRequestWorkers setting is too high.
        Your recommended MaxRequestWorkers setting is between 168 and 187.		<------- Acceptable Range (10% of MAX)
        Max potential memory usage:                                   1354 MB
        Percentage of TOTAL RAM allocated to Apache:                  136.38  %
        Percentage of REMAINING RAM allocated to Apache:              136.38  %
--------------------------------------------------------------------------------
```

# 子プロセスのメモリ出力
## shared_memory_size.pl (naoya版)
[Linux のプロセスが Copy on Write で共有しているメモリのサイズを調べる - naoyaのはてなダイアリー](http://d.hatena.ne.jp/naoya/20080212/1202830671)
大変よく参照されるが、以下の shared_memory_size.pl は Linux::Smaps が必要なため、他のスクリプトを利用する方が良いだろう。

```bash:スクリプト
cat << _EOF_ > shared_memory_size.pl
#!/usr/bin/env perl
use strict;
use warnings;
use Linux::Smaps;

@ARGV or die "usage: %0 [pid ...]";

printf "PID\tRSS\tSHARED\n";

for my \$pid (@ARGV) {
    my \$map = Linux::Smaps->new(\$pid);
    unless (\$map) {
        warn \$!;
        next;
    }

    printf
        "%d\t%d\t%d (%d%%)\n",
        \$pid,
        \$map->rss,
        \$map->shared_dirty + \$map->shared_clean,
        int(((\$map->shared_dirty + \$map->shared_clean) / \$map->rss) * 100)
}
_EOF_
```
```bash:コマンド
perl shared_memory_size.pl `pgrep httpd`
```
```bash:出力例
PID     RSS     SHARED
4847    8272    6180 (74%)
4876    5544    5292 (95%)
4935    5544    5292 (95%)
4976    5544    5292 (95%)
4999    5544    5292 (95%)
```

## shared_memory_size.pl (yumatsumo版)
[プロセスのCoW共有しているメモリのサイズ - マツモブログ](http://d.hatena.ne.jp/yumatsumo/20110805)
`Linux::Smaps` 不要版。

```bash:スクリプト
cat << _EOF_ > shared_memory_size.pl
#!/bin/env perl

use strict;
use warnings;

use List::Util ();

@ARGV or die "usage: %0 [pid ...]";

my @output;

for my \$pid (@ARGV) {
   die "invalid pid '\$pid'" if \$pid =~ /\D/;
   my @smaps = \`cat /proc/\$pid/smaps\`;
   die if \$? != 0;
   my @shared = map { /(\d+)\s+kB/; \$1 } grep { /^Shared_(Clean|Dirty)/ } @smaps;
   my \$shared_total = List::Util::sum(@shared);
   my @rss = map { /(\d+)\s+kB/; \$1 } grep { /^Rss/ } @smaps;
   my \$rss_total = List::Util::sum(@rss);
   my \$parcent = sprintf '(%d %%)', int((\$shared_total / \$rss_total) * 100);
   push @output, [\$pid, \$rss_total, \$parcent];
}

unshift @output, [qw(PID RSS SHARED)];

for my \$out (@output) {
    print join "\t", @\$out;
    print "\n";
}
_EOF_
```
```bash:コマンド
perl shared_memory_size.pl `pgrep httpd`
```
```bash:出力例
PID     RSS     SHARED
2505    3460    (75 %)
2506    2960    (93 %)
2507    2960    (93 %)
2508    2960    (93 %)
2509    2960    (93 %)
2510    2960    (93 %)
```


## getmem.sh / getshmem.sh
[Apacheのメモリ使用量を調べる - satooshi@blog](http://blog.satooshi.jp/blog/2009/12/09/monitoring-memory-usage-of-apache/)

#### getmem.sh
```bash:スクリプト
cat << _EOF_ > getmem.sh
#!/bin/sh

#1 process name
if [ \$# -ne 1 ]; then
  exit
fi

# print header
printf "PID\tVmPeak\tVmSize\tVmHWM\tVmRSS\n"

# get memory size
pid=\`pgrep \$1\`

for p in \$pid
do
  if [ -f /proc/\$p/status ]; then
    vm=\`grep -e '^VmPeak:\|VmSize:\|VmHWM:\|VmRSS:' /proc/\$p/status | awk '{print \$2}'\`
    printf "%d\t%d\t%d\t%d\t%d\n" \$p \$vm
  fi
done
_EOF_
```
```bash:コマンド
sh getmem.sh httpd
```
```bash:出力例
PID     VmPeak  VmSize  VmHWM   VmRSS
2193	307688	307688	11828	11828
2194	307708	307688	6096	6096
2195	307708	307688	6096	6096
2196	307708	307688	6096	6096
2197	307708	307688	6096	6096
2198	307708	307688	6096	6096
```

#### getshmem.sh
```bash:スクリプト
cat << _EOF_ > getshmem.sh
#!/bin/sh

# \$1 process name
if [ \$# -ne 1 ]; then
  exit
fi

# print header
printf "PID\tRSS\tSHARED\n"

# get shared memory size
pid=\`pgrep \$1\`

for p in \$pid
do
  if [ -f /proc/\$p/smaps ]; then
    vm=\`grep -e '^Rss:\|^Shared_Clean:\|^Shared_Dirty:' /proc/\$p/smaps |
    awk '
      BEGIN {
        rss = 0;
        clean = 0;
        dirty = 0;
      }
      {
        if(\$1 == "Rss:") {
          rss += \$2;
        }
        else if(\$1 == "Shared_Clean:") {
          clean += \$2;
        }
        else if(\$1 == "Shared_Dirty:") {
          dirty += \$2;
        }
      }
      END {
        per = (rss == 0) ? 0 : (clean+dirty)*100/rss;
        printf("%d\t%d\t%d\n", rss, clean+dirty, per);
      }
    '\`

    printf "%d\t%d\t%d (%d%%)\n" \$p \$vm
  fi
done
_EOF_
```
```bash:コマンド
sh getshmem.sh httpd
```
```bash:出力例
PID     RSS     SHARED
2193	11832	8368 (70%)
2194	6208	6012 (96%)
2195	6208	6016 (96%)
2196	6208	6016 (96%)
2197	6208	6016 (96%)
2198	6208	6016 (96%)
```

## smaps.sh
[hollyなblog:2010-09-23の技術こねた](http://blog.livedoor.jp/kurt0027/archives/52376188.html)
シェルスクリプト版。Perl も Linux::Smaps も不要。
単位としてKB、項目としてVSZ(Virtual Memory Size)が追加されている。

```bash:スクリプト
cat << _EOF_ > smaps.sh
#!/bin/sh

echo -e "PID\tVSZ\t\tRSS\tShared"
for pid in \$@; do
    smaps="/proc/\$pid/smaps"
    vsz=\$(grep -E "^Size" \$smaps | awk 'BEGIN{ num = 0 } { num += \$2 } END{ print num }')
    rss=\$(grep -E "^Rss" \$smaps | awk 'BEGIN{ num = 0 } { num += \$2 } END{ print num }')
    shared=\$(grep -E "^Shared" \$smaps | awk 'BEGIN{ num = 0 } { num += \$2 } END{ print num }')
    percent=\$(echo "scale=2; (\$shared / \$rss) * 100" | bc | cut -d "." -f 1)
    echo -e "\$pid\t\${vsz}KB\t\${rss}KB\t\${shared}KB(\${percent}%)"
done
_EOF_
```
```bash:コマンド
sh smaps.sh `pgrep httpd`
```
```bash:出力例
PID     VSZ             RSS     Shared
4847	232840KB        8272KB  6180KB(74%)
4876	232840KB        5544KB  5292KB(95%)
4935	232840KB        5544KB  5292KB(95%)
4976	232840KB        5544KB  5292KB(95%)
4999	232840KB        5544KB  5292KB(95%)
```


## none_shared_memory_fetcher.pl
[Apacheとかforkしたプロセスのメモリチューニングに関するメモとスクリプト | hirobanex.net](http://hirobanex.net/article/2013/10/1381407737)
VSZ の代わりに NONE_SHARED が計算、出力されている。

```bash:スクリプト
cat << _EOF_ > none_shared_memory_fetcher.pl
#!/usr/bin/env perl
use strict;
use warnings;

@ARGV or die "usage: %0 [pid ...]";

printf "PID\tRSS\tSHARED\tNONE_SHARED\n";

for my \$pid (@ARGV) {
    open my \$fh, "< /proc/\$pid/smaps" or (warn \$! and next);
    my @rows = <\$fh>;
    my \$rss    = mem_size_fetcher('Rss',@rows) or next;
    my \$shared = mem_size_fetcher('Shared_Clean',@rows) + mem_size_fetcher('Shared_Dirty',@rows);
    printf
        "%d\t%d\t%d (%d%%)\t%d\n",
        \$pid,
        \$rss,
        \$shared,
        int((\$shared / \$rss) * 100),
        (\$rss - \$shared),
}

sub mem_size_fetcher {
    my (\$target,@rows) = @_;
    my \$mem_size = 0;
    for my \$row (@rows) {
        my (\$mem) = \$row =~ /^\$target:\s*(\d+)/i;
        \$mem_size +=(\$mem||0);
    }
    return \$mem_size;
}
_EOF_
```
```bash:コマンド
perl none_shared_memory_fetcher.pl `pgrep httpd`
```
```bash:出力例
PID     RSS     SHARED          NONE_SHARED
12538	3780	3072 (81%)      708
12540	3048	2836 (93%)      212
12541	3060	2840 (92%)      220
12542	3152	2908 (92%)      244
12543	3060	2840 (92%)      220
12544	3048	2836 (93%)      212
12545	3060	2840 (92%)      220
12546	3060	2840 (92%)      220
12547	3060	2840 (92%)      220
```

# 自動計算
## get_max_client.php
[apacheのMaxClientを算出するスクリプトを作った - webネタ](http://r-h.hatenablog.com/entry/20110317)

```bash:コマンド
curl -sL https://raw.githubusercontent.com/ryoppy/Get-MaxClients./master/get_max_client.php | php
```
```bash:出力例
--------------------------------
memorySize / (rssAverage - shrAverage) = 1020076KB / (5673KB - 5364KB) = 3301
MaxClient maximum value is 3301.
--------------------------------
```

### kenjis フォーク
```bash:コマンド
curl -sL https://raw.githubusercontent.com/kenjis/Get-MaxClients./my_change/get_max_client.php | php
```
```bash:出力例
--------------------------------
rssAverage - shrAverage = (5673KB - 5364KB) = 309KB
memorySize / (rssAverage - shrAverage) = 1020076KB / 309KB = 3301
MaxClient maximum value is 3301.
--------------------------------
```

## get_max_clients.sh
[Apacheチューニング: MaxClientsに設定できる上限値を計算する - (DxD)∞](http://dxd8.com/archives/221/)
**get_max_client.php** のシェルスクリプト版。 `$_MIN_MAX_CLIENTS` については以下の通り。
> 空きメモリ量からMaxClientsを算出しています。ただし、起動済みのApacheプロセスが使用しているメモリ量を考慮していないので不完全です。


```bash:スクリプト
cat << _EOF_ > get_max_clients.sh
#!/bin/bash

_PIDS=(\`pgrep httpd\`)
_PROC_COUNT=\${#_PIDS[@]}

_MEMORY_TOTAL=\`free | grep Mem | awk '{print \$2;};'\`
_MEMORY_FREE=\`vmstat -a | awk 'NR==3{print \$4+\$5;};'\`
_RSS_TOTAL=0
_SHARED_TOTAL=0

for _PID in \${_PIDS[@]}; do
    _SMAPS=\`cat /proc/\$_PID/smaps\`
    _RSS=\`echo "\$_SMAPS" | grep Rss | awk '{value += \$2} END {print value;};'\`
    _SHARED=\`echo "\$_SMAPS" | grep Shared | awk '{value += \$2} END {print value;};'\`
    _RSS_TOTAL=\`expr \$_RSS_TOTAL + \$_RSS\`
    _SHARED_TOTAL=\`expr \$_SHARED_TOTAL + \$_SHARED\`
done

_RSS_AVERAGE=\`expr \$_RSS_TOTAL / \$_PROC_COUNT\`
_SHARED_AVERAGE=\`expr \$_SHARED_TOTAL / \$_PROC_COUNT\`
_PROC_MEMORY=\`expr \$_RSS_AVERAGE - \$_SHARED_AVERAGE\`
_MIN_MAX_CLIENTS=\`expr \$_MEMORY_FREE / \$_PROC_MEMORY\`
_MAX_MAX_CLIENTS=\`expr \$_MEMORY_TOTAL / \$_PROC_MEMORY\`

echo "Memory Total / (Rss Average - Shr Average) = \$_MEMORY_TOTAL / (\$_RSS_AVERAGE - \$_SHARED_AVERAGE)"
echo "Memory Free  / (Rss Average - Shr Average) = \$_MEMORY_FREE / (\$_RSS_AVERAGE - \$_SHARED_AVERAGE)"
echo "MaxClients = \$_MIN_MAX_CLIENTS ~ \$_MAX_MAX_CLIENTS"

exit 0
_EOF_
```
```bash:コマンド
sh get_max_clients.sh
```
```bash:出力例
Memory Total / (Rss Average - Shr Average) = 1020076 / (3626 - 3263)
Memory Free  / (Rss Average - Shr Average) = 635560 / (3626 - 3263)
MaxClients = 1750 ~ 2810
```

# Apache 関連情報
* [Apacheセキュリティ設定](http://qiita.com/bezeklik/items/1c4145652661cf5b2271)
* [Apache HTTP Server のサポート期限](http://qiita.com/bezeklik/items/7e2c02c3eabd2a416231)
* [Apache のログをコマンドラインで集計する](http://qiita.com/bezeklik/items/f5c292c4360cde140bef)
* [Apache の情報をコマンドラインで取得する](http://qiita.com/bezeklik/items/7bbfcbcfeb05c1d57fec)
* [IPアドレスから国や都市を判別する (GeoIP)](http://qiita.com/bezeklik/items/927e1c52c8c0aefb1f06)
* [Apache チューニング スクリプト](http://qiita.com/bezeklik/items/85f4001a2517be5fe4c0)
* [Apache の MaxClients の適正値](http://qiita.com/bezeklik/items/14e27417f9cc93ca7222)