読者です 読者をやめる 読者になる 読者になる

PostgreSQL リハビリ

PostgreSQL

7.3以降、久しぶりにPostgreSQL案件に携わることになったので、リハビリのメモ。

  • PostgreSQL本体は、Windows版のバイナリパッケージでお気軽インストール。

    • Windowsサービスとしてインストールされます。
    • インストール中にデータベース・クラスタも作成されます。
    • ロケールは「C」にしておきます。
  • クライアントのpsqlはソースからCygwinでビルドしてみました。

    • 依存パッケージが複数あります。私は特に「readline」で突っ掛かりました。→ 「readline-devel」で解決。
    • psql.exe が生成されるので「alias psql='/usr/local/pgsql/bin/psql.exe'」とします。
$ tar zxvf ./postgresql-9.3.5.tar.gz
$ cd ./postgresql-9.3.5/

# 国際化対応(日本語)とマルチスレッド対応オプションを付けてコンパイル
$ ./configure --enable-nls --enable-thread-safety
$ cd ./src/bin
$ make

# crypt.c エラー: 不明な型名 ‘__int64’ です
→ 「#define B64 __int64」を「#define B64 long long」に変更しました。

$ make install
$ cd ../interfaces/libpq/
$ make install

接続確認

$ psql -d postgres -h localhost -p 5432 -U postgres
ユーザ postgres のパスワード:
postgres=# select version();
                           version
-------------------------------------------------------------
 PostgreSQL 9.3.5, compiled by Visual C++ build 1600, 64-bit
(1 行)

プロンプトを変更したり、Nullの表示を変更したり。

\set PROMPT1 '%n@%/%# '
\pset null '(NULL)'
  • psql終了 → メタコマンド「\q」
  • データベース一覧 → メタコマンド「\l」 : 初期状態では、postgres, template0, template1 のみ
  • ロール一覧 → メタコマンド「\du」 : 初期状態では、postgres のみ
  • テーブルスペース → メタコマンド「\db+」 : 初期状態では、pg_default, pg_global のみ
  • テーブル等の一覧 → メタコマンド「\dt」
  • テーブル定義を確認 → メタコマンド「\d テーブル名;」
  • スキーマ一覧 → メタコマンド「\dn」 : 初期状態では、public のみ

ロール管理

SQL# CREATE ROLE app_user WITH LOGIN PASSWORD 'xxxxx';
CREATE ROLE

SQL# DROP ROLE app_user;
DROP ROLE

SQL# CREATE USER app_user WITH PASSWORD 'xxxxx';
CREATE ROLE

テーブルスペースを作成

  • 作成したテーブルスペースにテーブルを作成 → CREATE TABLE ... TABLESPACE ...
-- Windowsでもパス区切りは「/」
SQL# CREATE TABLESPACE table_space_1 LOCATION 'c:/PostgreSQL/9.3/data';
CREATE TABLESPACE

SQL# \db+
                         テーブルスペース一覧
     名前      |  所有者  |          場所          | アクセス権 | 説明
---------------+----------+------------------------+------------+------
...
 table_space_1 | postgres | c:\PostgreSQL\9.3\data |            |

データベース作成

SQL# CREATE DATABASE example_db WITH OWNER app_user TABLESPACE table_space_1;
CREATE DATABASE

スキーマ作成

SQL> CREATE SCHEMA app;
CREATE SCHEMA

スキーマについて

SQL> SHOW search_path;
  search_path
----------------
 "$user",public
(1 行)

SQL> SET search_path TO app,public;
SET
  • スキーマ検索パス
    • 「SHOW search_path;」で確認できます
    • デフォルトでは、user名と同じ名前のスキーマ("$user")が最優先されます。
    • 「SET search_path TO xxxx,yyyy」で設定できます。(postgresql.confでも可)
    • カレントスキーマは「select current_schema();」で確認できます。CREATE TABLE でスキーマ名を指定しない場合はカレントスキーマにテーブルが作成されます。

Doctrine2 DBAL フェッチのサンプル

MySQL Doctrine2

実装コード

class TestDoctrineDbalFetchCommand extends ContainerAwareCommand
{
    const EXIT_SUCCESS = 0;
    const EXIT_FAILURE = 1;

    const THROUGHPUT = 5;

    protected function configure()
    {
        $this->setName('sample:dbal-fetch');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $sql = <<< SQL
SELECT
  id, DATE_FORMAT(record_date, '%Y/%m/%d %H:%i:%S') as r_date, temperature
FROM
  mercury2
WHERE
  record_date > :record_date
ORDER BY
  record_date DESC
SQL;

        $output->writeln("-- 処理単位件数 = " . self::THROUGHPUT);

        try {

            $conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
            $conn->connect();   // 接続エラーの場合はここで例外が発生します

            $date = new \DateTime("2014-09-01");

            $stmt = $conn->prepare($sql);
            $stmt->bindValue(':record_date', $date, 'datetime');
            if (!$stmt->execute()) {
                throw new \RuntimeException("execute() returned false.");
            }

            $count = 0;
            while ($row = $stmt->fetch()) {
                if ($count === 0) {
                    $output->writeln("-- フェッチを開始します");
                }
                $count++;

                $output->writeln(sprintf("%02d : %s, %s, %s",
                    $count, $row['id'], $row['r_date'], $row['temperature']));

                if ($count % self::THROUGHPUT === 0) {
                    $output->writeln("-- 現在 ${count} 件目です");
                }
            }

            $conn->close();
            $output->writeln("-- 合計 ${count} 件を処理しました");

            return self::EXIT_SUCCESS;

        } catch (\Exception $e) {
            $conn->close();
            $output->writeln("-- 例外が発生しました : " . $e->getMessage());
            return self::EXIT_FAILURE;
        }

    }
}

実行結果

$ php app/console sample:dbal-fetch
-- 処理単位件数 = 5
-- フェッチを開始します
01 : 554, 2014/09/12 00:00:00, 21
02 : 553, 2014/09/11 00:00:00, 20
03 : 552, 2014/09/10 00:00:00, 21
04 : 551, 2014/09/09 00:00:00, 22
05 : 550, 2014/09/08 00:00:00, 20
-- 現在 5 件目です
06 : 549, 2014/09/07 00:00:00, 20
07 : 548, 2014/09/06 00:00:00, 26
08 : 547, 2014/09/05 00:00:00, 26
09 : 546, 2014/09/04 00:00:00, 23
10 : 545, 2014/09/03 00:00:00, 23
-- 現在 10 件目です
11 : 544, 2014/09/02 00:00:00, 23
-- 合計 11 件を処理しました

SwiftMailとISO-2022-JPとワードラップ

PHP SwiftMail

SwiftMailでISO-2022-JPな日本語メールを送信する際に、次のような問題が生じる場合があります。

  • 文字化け
  • ワードラップ

Swift MailerでISO-2022-JPなメールが送れなくなっていた件について - polidog lab++」を 参考にさせて頂きました。

検証コード

class Message extends \Swift_Message
{
    public static function newInstance($subject = null, $body = null, $contentType = null, $charset = null)
    {
        return new self($subject, $body, $contentType, $charset);
    }

    public function setSubject($subject)
    {
        $subject = $this->_convertString($subject);
        parent::setSubject($subject);
        return $this;
    }

    protected function _convertString($string)
    {
        $charset = strtolower($this->getCharset());
        if (!in_array($charset, array('utf-8','iso-8859-1', ''))) {
            $string = mb_convert_encoding($string, $charset, 'utf-8');
        }
        return $string;
    }
}

class TestSwiftMailCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName('sample:swiftmail');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $subject = $body = 'あいうえおかきくけこさしすせそたちつてとABC日本語 ワードラップされる?';

        $message = Message::newInstance()
            ->setCharset('iso-2022-jp')
            ->setEncoder(\Swift_Encoding::get7BitEncoding())
            ->setFrom(array('...'))
            ->setTo('...')
            ->setSubject($subject)
            ->setMaxLineLength(0)    // 折り返しはココで調整
            ->setBody($body);

        $container = $this->getContainer();
        $mailer = $container->get('mailer');
        $mailer->send($message);

        $spool = $mailer->getTransport()->getSpool();
        $transport = $container->get('swiftmailer.transport.real');
        $spool->flushQueue($transport);
    }
}

Doctrine2 デバッグモードとEntityManager::clear()について

PHP Doctrine2 Symfony2

Doctrine2のパフォーマンスとメモリ使用量について、「デバッグモード」と「EntityManager::clear()」を検証します。

結論

以下の条件を満たす場合、パフォーマンスとメモリ使用量に影響が出るようです。

  • env が prod 以外で app/console の「--no-debug」オプションを付けない場合
  • 大量データを処理する際に EntityManager::clear() を呼んでいない場合

検証コード

class xxxxCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName('sample:doctrine-batch');
        $this->addOption('no-clear', null, InputOption::VALUE_NONE);
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $batchSize = 500;
        $em = $this->getContainer()->get('doctrine')->getManager();

        $stopwatch = new Stopwatch();
        $stopwatch->start('doctrine');

        for ($i = 1; $i <= 10000; ++$i) {

            $entity = new Entity();
            $entity->setXxxx(...);
            ...
            $em->persist($entity);

            if (($i % $batchSize) == 0) {
                $event = $stopwatch->lap('doctrine');
                $em->flush();
                if (!$input->getOption('no-clear')) {
                    $em->clear();    // ★ココ
                }
                printf("%8d: %8.3f sec %8.2f MB\n", $i, $event->getDuration() / 1000,
                    floatval($event->getMemory() / 1024 / 1024));
            }
        }

        $stopwatch->stop('doctrine');
        $em->flush();
        $em->clear();
        $em->close();
    }
}

no-debugモード + EntityManager::clear() あり

$ php app/console sample:doctrine-batch --no-debug
     500:    0.204 sec    13.50 MB
    1000:    0.964 sec    14.25 MB
    1500:    1.663 sec    14.50 MB
    2000:    2.421 sec    14.50 MB
    2500:    3.117 sec    14.50 MB
    3000:    3.832 sec    14.50 MB
    3500:    4.690 sec    14.50 MB
    4000:    5.431 sec    14.50 MB
    4500:    6.159 sec    14.50 MB
    5000:    6.861 sec    14.50 MB
    5500:    7.593 sec    14.50 MB
    6000:    8.326 sec    14.50 MB
    6500:    9.037 sec    14.50 MB
    7000:    9.735 sec    14.50 MB
    7500:   10.473 sec    14.50 MB
    8000:   11.215 sec    14.50 MB
    8500:   11.917 sec    14.50 MB
    9000:   12.646 sec    14.50 MB
    9500:   13.382 sec    14.50 MB
   10000:   14.097 sec    14.50 MB

no-debugモード + EntityManager::clear() なし

$ php app/console sample:doctrine-batch --no-debug --no-clear
     500:    0.203 sec    13.50 MB
    1000:    0.959 sec    14.50 MB
    1500:    1.743 sec    15.00 MB
    2000:    2.639 sec    15.75 MB
    2500:    3.568 sec    16.00 MB
    3000:    4.545 sec    16.50 MB
    3500:    5.609 sec    17.00 MB
    4000:    6.720 sec    18.00 MB
    4500:    7.930 sec    18.50 MB
    5000:    9.166 sec    19.00 MB
    5500:   10.487 sec    19.50 MB
    6000:   11.896 sec    20.00 MB
    6500:   13.339 sec    20.50 MB
    7000:   14.887 sec    21.00 MB
    7500:   16.515 sec    21.50 MB
    8000:   18.182 sec    22.25 MB
    8500:   19.894 sec    22.75 MB
    9000:   21.713 sec    23.25 MB
    9500:   23.608 sec    23.75 MB
   10000:   25.574 sec    24.25 MB

debugモード + EntityManager::clear() あり

$ php app/console sample:doctrine-batch
     500:    0.209 sec    13.50 MB
    1000:    2.120 sec    15.75 MB
    1500:    4.154 sec    17.00 MB
    2000:    6.052 sec    18.00 MB
    2500:    7.926 sec    19.50 MB
    3000:    9.830 sec    20.75 MB
    3500:   11.806 sec    21.75 MB
    4000:   13.900 sec    23.00 MB
    4500:   15.802 sec    24.25 MB
    5000:   17.684 sec    25.50 MB
    5500:   19.617 sec    26.50 MB
    6000:   21.513 sec    27.75 MB
    6500:   23.447 sec    28.75 MB
    7000:   25.680 sec    29.75 MB
    7500:   27.579 sec    31.00 MB
    8000:   29.386 sec    32.00 MB
    8500:   31.270 sec    33.75 MB
    9000:   33.100 sec    35.00 MB
    9500:   35.112 sec    36.00 MB
   10000:   36.941 sec    37.00 MB

env=prod + EntityManager::clear() あり

$ php app/console sample:doctrine-batch --env=prod
     500:    0.209 sec    12.50 MB
    1000:    0.971 sec    13.50 MB
    1500:    1.698 sec    13.75 MB
    2000:    2.408 sec    13.75 MB
    2500:    3.099 sec    13.75 MB
    3000:    3.790 sec    13.75 MB
    3500:    4.514 sec    13.75 MB
    4000:    5.194 sec    13.75 MB
    4500:    5.945 sec    13.75 MB
    5000:    6.652 sec    13.75 MB
    5500:    7.522 sec    13.75 MB
    6000:    8.219 sec    13.75 MB
    6500:    8.927 sec    13.75 MB
    7000:    9.639 sec    13.75 MB
    7500:   10.350 sec    13.75 MB
    8000:   11.055 sec    13.75 MB
    8500:   11.769 sec    13.75 MB
    9000:   12.456 sec    13.75 MB
    9500:   13.142 sec    13.75 MB
   10000:   13.834 sec    13.75 MB

env=prod + EntityManager::clear() なし

$ php app/console sample:doctrine-batch --env=prod --no-clear
     500:    0.199 sec    12.50 MB
    1000:    0.965 sec    13.75 MB
    1500:    1.747 sec    14.25 MB
    2000:    2.606 sec    14.75 MB
    2500:    3.521 sec    15.25 MB
    3000:    4.513 sec    15.75 MB
    3500:    5.560 sec    16.25 MB
    4000:    6.670 sec    17.25 MB
    4500:    7.822 sec    17.50 MB
    5000:    9.068 sec    18.00 MB
    5500:   10.375 sec    18.50 MB
    6000:   11.733 sec    19.00 MB
    6500:   13.159 sec    19.50 MB
    7000:   14.662 sec    20.00 MB
    7500:   16.323 sec    20.50 MB
    8000:   17.975 sec    21.25 MB
    8500:   19.685 sec    21.75 MB
    9000:   21.455 sec    22.50 MB
    9500:   23.266 sec    23.00 MB
   10000:   25.168 sec    23.25 MB

Consoleコンポーネントをスタンドアロンで使ってみます

PHP Symfony2

Symfony2のConsoleコンポーネントスタンドアロンで使ってみます

composer.json

{
    "autoload": {
        "psr-0": {"": "src/"}
    },
    "require": {
        "symfony/console": "dev-master"
    }
}

セットアップ

$ curl -O http://getcomposer.org/composer.phar
$ php composer.phar update
$ mkdir app
$ mkdir -p src/Command
$ tree -d
.
├── app
├── src
│   └── Command
└── vendor
    ├── composer
    └── symfony
        └── console
            └── Symfony
                └── Component
                    └── Console
                        ├── Command
                        ├── Descriptor
                        ├── Event
                        ├── Formatter
                        ├── Helper
                        ├── Input
                        ├── Logger
                        ├── Output
                        ├── Question
                        ├── Resources
                        │   └── bin
                        ├── Tester
                        └── Tests

app/console ランチャー

#!/usr/bin/env php
<?php

require_once __DIR__ . '/../vendor/autoload.php';

use Symfony\Component\Console\Application;

$app = new Application();

foreach (glob(__DIR__ . '/../src/Command/*Command.php') as $FileName) {
    $className = "Command\\" . rtrim(basename($FileName), ".php");
    $app->addCommands(array(new $className()));
}

$app->run();

src/Command/*Command.php コマンド実装クラス

<?php

namespace Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class SampleCommand extends Command
{
    protected function configure()
    {
        $this->setName('sample');
        $this->setDescription('これはサンプルコマンドです');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln("ドイツパンは美味しい!");
    }
}

実行結果

$ php app/console sample
ドイツパンは美味しい!

便利だけどパッケージがもう少しコンパクトだといいなぁ...

Vagrant(ベイグラント)の備忘録

準備

仮想マシンのセットアップとブート

以下、Cygwin上で作業しました。

Boxの追加

$ vagrant box add centos https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box
Downloading or copying the box...
Extracting box...ate: 402k/s, Estimated time remaining: --:--:--)
Successfully added box 'centos' with provider 'virtualbox'!

Boxの一覧

$ vagrant box list
centos (virtualbox)

仮想マシンの初期化

  • Usage: vagrant init [box-name]
  • 初期化が終わるとディレクト内に Vagrantfile が生成されます
$ mkdir ./centos
$ cd ./centos/

$ vagrant init centos
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

仮想マシンのブート

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'centos'...
[default] Matching MAC address for NAT networking...
[default] Setting the name of the VM...
[default] Clearing any previously set forwarded ports...
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2222 (adapter 1)
[default] Booting VM...
[default] Waiting for machine to boot. This may take a few minutes...
[default] Machine booted and ready!
[default] Mounting shared folders...
[default] -- /vagrant

仮想マシンの状態確認

$ vagrant status
Current machine states:

default                   running (virtualbox)

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.

ログインして起動を確認

  • Usage: vagrant ssh
  • poderosaなどのターミナルアプリケーションから接続する場合
    • vagrant ユーザが用意されている様です。
    • 接続ポートは 2222 みたいです。
    • 鍵ファイルは C:\Users\ユーザ名\.vagrant.d\insecure_private_key です。
$ vagrant ssh
[vagrant@vagrant-centos65 ~]$ uname -a
Linux vagrant-centos65.vagrantup.com 2.6.32-431.3.1.el6.x86_64 #1 SMP Fri Jan 3 21:39:27 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

[vagrant@vagrant-centos65 ~]$ whoami
vagrant

[vagrant@vagrant-centos65 ~]$ exit
logout
Connection to 127.0.0.1 closed.

仮想マシンのシャットダウン

Usage: vagrant halt

$ vagrant halt
[default] Attempting graceful shutdown of VM...


$ vagrant status
Current machine states:

default                   poweroff (virtualbox)

The VM is powered off. To restart the VM, simply run `vagrant up`

【トラブルシュート】vagrant up でエラーが出た場合

環境変数「VBOX_INSTALL_PATH」が定義されていない場合、次の様なエラーが出ました。
→ VBOX_MSI_INSTALL_PATH と同じ値を設定します。

$ env | grep -i VBOX
VBOX_INSTALL_PATH=D:\VirtualBox\

$ vagrant up
Vagrant could not detect VirtualBox! Make sure VirtualBox is properly installed.
Vagrant uses the `VBoxManage` binary that ships with VirtualBox, and requires
this to be available on the PATH. If VirtualBox is installed, please find the
`VBoxManage` binary and add it to the PATH environmental variable.

※ 環境変数 VBOX_INSTALL_PATH を設定 (システム > システムの詳細設定 > 環境変数)

$ env | grep -i VBOX
VBOX_INSTALL_PATH=D:\VirtualBox\
VBOX_MSI_INSTALL_PATH=D:\VirtualBox\

$ vagrant up
...
  • VBoxManage.exe を探してこれが見つからないと「VirtualBoxを検出できません」とする様です。
  • Vagrant/embedded/gems/gems/vagrant-1.3.5/templates/locales/en.yml に前述のエラーメッセージが定義されています。
en:
  vagrant:
    errors:
      virtualbox_not_detected: |-
        Vagrant could not detect VirtualBox! Make sure VirtualBox is properly installed.
        Vagrant uses the `VBoxManage` binary that ships with VirtualBox, and requires
        this to be available on the PATH. If VirtualBox is installed, please find the
        `VBoxManage` binary and add it to the PATH environmental variable.
if Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.cygwin?
@logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage")

# On Windows, we use the VBOX_INSTALL_PATH environmental
# variable to find VBoxManage.
if ENV.has_key?("VBOX_INSTALL_PATH")
  # Get the path.
  path = ENV["VBOX_INSTALL_PATH"]
  @logger.debug("VBOX_INSTALL_PATH value: #{path}")

Symfony2 PHPエラーを捕まえます

PHP Symfony2

Debugコンポーネント を利用して、PHPエラーを捕まえてみようと思います。

You should never enable the debug tools in a production environment as they might disclose sensitive information to the user.

ということなので、カスタマイズした例外ハンドラに置き換えます。

実装コード

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\ErrorHandler;

class YourExceptionHandler extends ExceptionHandler
{
    public function handle(\Exception $e)
    {
        // error_log() など
        echo 'PHPエラーが発生しました -- ' . $e->getMessage() . PHP_EOL;

        echo new Response('sorry.', 500);
    }
}

class TestErrorHandlerCommand extends Command
{
    protected function configure()
    {
        $this->setName('sample:error-handler');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // ErrorHandler クラスは、PHPのエラーを検出し、例外に変換してスローします
        ErrorHandler::register();

        YourExceptionHandler::register();

        try {

            $nonObject = null;
            $nonObject->method();

        } catch (\Exception $e) {

            // FatalErrorException はキャッチできません

        }
    }
}

実行結果

$ php app/console sample:error-handler --env=prod
PHPエラーが発生しました -- Error: Call to a member function method() on a non-object
in ...\Command\TestErrorHandlerCommand.php line 39
HTTP/1.0 500 Internal Server Error
Cache-Control: no-cache
Date:          Fri, 24 Oct 2014 14:51:43 GMT

sorry.
$ php app/console sample:error-handler --env=dev
PHPエラーが発生しました -- Error: Call to a member function method() on a non-object
in ...\Command\TestErrorHandlerCommand.php line 39
HTTP/1.0 500 Internal Server Error
Cache-Control: no-cache
Date:          Fri, 24 Oct 2014 14:52:35 GMT

sorry.PHPエラーが発生しました -- Error: Call to a member function method() on a non-object
in ...\Command\TestErrorHandlerCommand.php line 39
HTTP/1.0 500 Internal Server Error
Cache-Control: no-cache
Date:          Fri, 24 Oct 2014 14:52:35 GMT

sorry.

Apache JMeterについて

テスト

Apache JMeter についての備忘録です。

起動

管理者権限で bin/jmeter.bat を実行します。

テスト計画の作成

スレッドグループの設定

テスト計画にスレッドグループを追加します。

  • スレッド数 : 同時実行数
  • ループ回数

HTTPリクエストの設定

スレッドグループに サンプラー > HTTPリクエスト を追加します。

  • JSONテキストをPOSTする場合は、Body Data のタブに切り替えて、JSONテキストを入力します。

リスナーの設定

HTTPリクエストにリスナーを追加します。

  • 結果をツリーで表示 : リクエストヘッダ/ボディ、レスポンスヘッダ/ボディを確認することができます。
  • 結果を表で表示 : 個々のリクエストについての応答時間、結果
  • Response Time Graph : 応答性の変動
  • 統計レポート : 総テスト回数、平均応答時間、最小応答時間と最大応答時間、エラー率、スループット
  • シンプルデータライタ : 他のリスナーを無効化して、これでデータを記録すると良さそうです

Content-Type、拡張ヘッダの指定

HTTPリクエストに 設定エレメント > HTTPヘッダマネージャ を追加します。

  • JSONテキストをPOSTする場合は、名前:Content-Type、値:application/json

BASIC認証

HTTPリクエストに 設定エレメント > HTTP 認証マネージャー を追加します。
※どうやらバグがあるらしく、ヘッダが挿入されませんでした。そんな場合は、自前でヘッダに設定します。

カスタマイズ

  • 特にリスナーにシンプルデータライタを使う際に。
  • bin/jmeter.properties を編集
    • jmeter.save.saveservice.timestamp_format :

phalconのインストール

phalcon がいい感じですよ」と伺ったので、xampp環境にインストールしてみます。

インストール

$ php -r "phpinfo();" | grep -i phalcon
phalcon
Phalcon Framework => enabled
Phalcon Version => 1.3.3
...

チュートリアル

$ php -r "print_r(get_loaded_extensions());" | egrep -i '(Core|libxml|filter|SPL|standard|phalcon|pdo_mysql)'
    [0] => Core
    [6] => filter
    [12] => SPL
    [17] => standard
    [22] => libxml
    [41] => pdo_mysql
    [49] => phalcon

構成はこんな感じみたいです。

$ tree
.
├── app
│   ├── controllers
│   │   ├── IndexController.php
│   │   └── SignupController.php
│   ├── models
│   │   └── Users.php
│   └── views
│       ├── index
│       │   └── index.phtml
│       └── signup
│           └── index.phtml
├── public
│   └── index.php
├── README.md
└── schemas
    └── tutorial.sql

試しにアクセスしてみます。

$ curl -i http://localhost/tutorial/
HTTP/1.1 200 OK
Date: Fri, 17 Oct 2014 13:38:32 GMT
Server: Apache/2.4.4 (Win32) OpenSSL/0.9.8y PHP/5.4.19
X-Powered-By: PHP/5.4.19
Content-Length: 59
Content-Type: text/html; charset=UTF-8

<h1>Hello!</h1><a href="/tutorial/signup">Sign Up Here!</a>

んー。

Symfony2 カスタムバリデータ

PHP Symfony2

Symfony2標準の日付バリデータ
(vendor/symfony/symfony/src/Symfony/Component/Validator/Constraints/DateValidator.php)は、
以下の様に書式が「YYYY-MM-DD」形式となっています。

const PATTERN = '/^(\d{4})-(\d{2})-(\d{2})$/';

年月文字列「YYMM」や月日文字列「MMDD」(Month/day )が有効値か検証したいので、制約とバリデータを自作します。

Symfony2のバリデーションのしくみ

  • Symfony\Component\Validator\Constraint
public function validatedBy()
{
    return get_class($this).'Validator';
}

「制約クラス . Validator」という制約バリデータクラスを探し、そのバリデータの validate() を実行します。

制約クラスを用意する

  • Symfony\Component\Validator\Constraint を継承します。
  • 「@Annotation」というアノテーションを付けます。
  • public $message を定義して、エラーメッセージを設定します。
use Symfony\Component\Validator\Constraint;

/**
 * 月日文字列「MMDD」制約クラス
 * @Annotation
 *
 */
class DateMonthDay extends Constraint
{
    public $message = '有効な月日文字列「MMDD」ではありません';
}
use Symfony\Component\Validator\Constraint;

/**
 * 年月文字列「YYMM」制約クラス
 * @Annotation
 *
 */
class DateYearMonth extends Constraint
{
    public $message = '有効な年月文字列「YYMM」ではありません';
}

制約バリデータクラスを実装する

  • Symfony\Component\Validator\ConstraintValidator を継承します。
  • validate($value, Constraint $constraint) を実装します。
    • 戻り値はありません。
    • 無効値を検出した場合は、addViolation() を使って、エラーメッセージをコンテキストに追加します。
    • バリデータはエラーメッセージの有無でバリデーションエラーの有無を決定します。
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Constraint;

class DateYearMonthValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint) {
        if (!(is_string($value) or is_numeric($value))) {
            return;
        }

        $date = '20' . $value . '01';

        $dt = \DateTime::createFromFormat('Ymd', $date);

        $isValid = ($dt && $dt->format('Ymd') == $date);
        if (!$isValid) {
            $this->context->addViolation($constraint->message);
        }
    }
}
class DateMonthDayValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint) {
        if (!(is_string($value) or is_numeric($value))) {
            return;
        }

        $date = '2000' . $value;

        $dt = \DateTime::createFromFormat('Ymd', $date);

        $isValid = ($dt && $dt->format('Ymd') == $date);
        if (!$isValid) {
            $this->context->addViolation($constraint->message);
        }
    }
}

テストコード

class CustomConstraintValidatorTest extends WebTestCase
{
    protected $validator;

    public function setUp()
    {
        static::$kernel = static::createKernel();
        static::$kernel->boot();

        $this->validator = static::$kernel->getContainer()->get('validator');
    }

    public function testSuccess()
    {
        $errors = $this->validator->validate(new DateString('0229'));
        $this->assertCount(0, $errors, '');
    }

    public function testFailure()
    {
        $errors = $this->validator->validate(new DateString('0230'));
        foreach ($errors as $error) {
            echo $error->getPropertyPath() . " - " . $error->getMessage() . PHP_EOL;
        }
        $this->assertNotCount(0, $errors, '');
    }
}