CakePHP3でもActiveRecord::Enumを使いたいので、CakeDC/EnumベースでPropertyEnumを作った

k1low.hatenablog.com

CakePHP2では GitHub - k1LoW/Enumm: Enumm: Enum(model) plugin for CakePHP を作って使っていたのですが、CakePHP3では動きません。

CakePHP3で使える同じようなPluginを探して、CakeDC/Enum を見つけたのですが、CONSTベースとConfigureベース、データベースのテーブルベースしかありませんでした。

というわけで、Enummと同じようにプロパティベースで動くようにCakeDC/Enumにストラテジを追加する形でPropertyEnumを作りました。

github.com

使い方

Tableに $enums プロパティを設定して、 ['strategy' => 'property'] することでCakeDC/Enumの他のストラテジと同じように利用可能です。

<?php

class ArticlesTable extends Table
{
    public $enums = [
        'status' => [
            'public' = 'Published';
            'draft' = 'Drafted';
            'archive' = 'Archived';
        ],
    ];

    public function initialize(array $config)
    {
        $this->addBehavior('PropertyEnum.Enum', ['lists' => [
            'status' => [
                'strategy' => 'property',
            ]
        ]]);
    }
}

AutoSetComponent

Enummの時と同様に $enums をViewに自動でsetしたいときは PropertyEnum\AutoSetComponent を読み込むことで可能です。

<?php
namespace App\Controller;

class AppController extends Controller
{
    public function initialize()
    {
        parent::initialize();
        $this->loadComponent('PropertyEnum.AutoSet');
    }
}

というわけで

CakePHP3でも気楽にEmunを使えるようになりました。

CakePHP3でもバリデーションをパターンでまとめられるようにPatternableValidatorを作った

CakePHP3になって、CakePHP2では MultivalidatableBehavior などで実現していたバリデーションのルールセットの切り替えがコア機能に入りました。これはうれしい。

ただ、Kagasawa-sanが作っていたcakeplus/ValidationPatternsBehavior.php で実現していたバリデーションのパターンの設定はコア機能ではできなかったので作ってみました。

github.com

Before

<?php
namespace App\Model\Table;

class UsersTable extends AppTable
{
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->allowEmpty('username')
            ->add('username', 'minLength4', [
                'rule' => ['minLength', 4],
                'message' => __('Validation Error: minLength4'),
            ])
            ->add('username', 'maxLength10', [
                'rule' => ['maxLength', 10],
                'message' => __('Validation Error: maxLength10'),
            ]);

        $validator
            ->allowEmpty('password');
    }
}

After

<?php
namespace App\Model\Table;

class UsersTable extends AppTable
{
    public function validationDefault(Validator $validator)
    {
        $validator
            ->addPattern('id', ['integer', 'allowEmptyWhenCreate']);

        $validator
            ->addPattern('username', ['allowEmpty', 'username_length']);

        $validator
            ->appPattern('password', ['allowEmpty']);
    }
}

非常にスッキリしますね。

使い方

AppTableなどで、以下のように_validatorClassを変更して、あとはcakeplus/ValidationPatternsBehavior.phpと同じように、バリデーションのパターンを登録していけばOKです。

<?php
namespace App\Model\Table;

class AppTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);
        $this->_validatorClass = '\PatternedValidator\Validation\Validator';
        \PatternedValidator\Validation\Validator::$validationPatterns = [
            'username_length' => [
                'minLength4' => [
                    'rule' => ['minLength', 4],
                    'message' => __('Validation Error: minLength4'),
                ],
                'maxLength10' => [
                    'rule' => ['maxLength', 10],
                    'message' => __('Validation Error: maxLength10'),
                ]
            ],
        ];
    }
}

これで多くなりがちなバリデーションライフもバッチリです。

というわけで、私のCakePHP3 Plugin生活、スタートです!

fluent-plugin-s3 + fluent-plugin-dstatのフォーマットで保存されていたS3上のログをEmbulkを使ってMySQLに投入してみた

というわけでEmbulkを使ってみたのでメモです。

対象のログ

  • Fluentdで収集
  • dstatの情報を fluent-plugin-dstat で取得
  • fluent-plugin-s3 を利用してS3に保存
  • gzip圧縮

つまりこんなログです。

2016-01-31T15:24:42Z  examplekey  {"hostname":"example.com","dstat":{"total cpu usage":{"usr":"0.201","sys":"0.134","idl":"99.532","wai":"0.134","hiq":"0.0","siq":"0.0"},"mem\
ory usage":{"used":"1212170240.0","buff":"313139200.0","cach":"2034069504.0","free":"246816768.0"},"load avg":{"1m":"0.090","5m":"0.080","15m":"0.090"},"net/total":{"recv":"108.\
867","send":"212.200"}}}

Embulkのインストール

まずはEmbulkをインストールします。

$ brew cask install java
$ brew install embulk

次に必要そうなPluginをインストールします。

以下は、とりあえず最終的に必要になったもの。

$ embulk gem install embulk-input-s3
$ embulk gem install embulk-output-mysql
$ embulk gem install embulk-parser-fluent-s3-log
$ embulk gem install embulk-filter-expand_json
$ embulk gem install embulk-filter-column

inとoutを適当にかいて embulk guess

まずは in がS3なのはわかります。

github.com

tmp.yml とかに

in:
  type: s3
  bucket: my-logs
  path_prefix: web/
  access_key_id: XXXXXXXXXXXXXXXXXXXXXX
  secret_access_key: XXXXXXXXXXXXXXXXXXXXXX
out:
  type: stdout

くらいを書いて

$ embulk guess tmp.yml -o config.yml

と実行するとあら不思議、「いい感じに」設定用ファイルのフォーマットを config.yml として生成してくれます。

Embulkの半分は優しさでできているのか?

生成された config.yml をPluginのドキュメントを見ながら修正して、 embulk preview config.yml でプレビュー。うまくいかないときは embulk guess で微調整していきます。

fluent-plugin-s3のフォーマットのファイルをいい感じにパースしてくれる embulk-parser-fluent-s3-log

fluent-plugin-s3で保存したログのフォーマットはタイムスタンプ、キー、JSONと並んでいたので、さてどうしたものかと思ったら「まさに」なプラグインがありました。

github.com

in:
  type: s3
  bucket: my-logs
  path_prefix: web/
  access_key_id: XXXXXXXXXXXXXXXXXXXXXX
  secret_access_key: XXXXXXXXXXXXXXXXXXXXXX
  decoders:
  - {type: gzip}
  parser:
    type: fluent-s3-log
    columns:
      - {name: hostname, type: string}
      - {name: dstat, type: string}
out:
  type: stdout

ネストされたJSONを展開してくれるembulk-filter-expand_json

カラム dstat の部分はネストしたJSONのままなので展開する必要があります。

こちらも「まさに」なプラグインがありました。

github.com

今回は dstat カラム部分を展開するので以下のように記述しました。

in:
  type: s3
  bucket: my-logs
  path_prefix: web/
  access_key_id: XXXXXXXXXXXXXXXXXXXXXX
  secret_access_key: XXXXXXXXXXXXXXXXXXXXXX
  decoders:
  - {type: gzip}
  parser:
    type: fluent-s3-log
    columns:
      - {name: hostname, type: string}
      - {name: dstat, type: string}
filters:
  - type: expand_json
    json_column_name: dstat
    root: "$."
    expanded_columns:
      - {name: "['total cpu usage'].usr", type: double}
      - {name: "['total cpu usage'].sys", type: double}
      - {name: "['total cpu usage'].idl", type: double}
      - {name: "['total cpu usage'].wai", type: double}
      - {name: "['total cpu usage'].hiq", type: double}
      - {name: "['total cpu usage'].siq", type: double}
      - {name: "['memory usage'].used", type: double}
      - {name: "['memory usage'].buff", type: double}
      - {name: "['memory usage'].cach", type: double}
      - {name: "['memory usage'].free", type: double}
      - {name: "['load avg'].1m", type: double}
      - {name: "['load avg'].5m", type: double}
      - {name: "['load avg'].15m", type: double}
      - {name: "['net/total'].recv", type: double}
      - {name: "['net/total'].send", type: double}
out:
  type: stdout

MySQLカラム名に合わせるために in 側のカラム名のリネーム

カラム名"['total cpu usage'].usr" のままだとMySQLカラム名にするのには微妙なので、カラム名をリネームします。

こちらも「まさに」なプラグインがありました。

github.com

in:
  type: s3
  bucket: my-logs
  path_prefix: web/
  access_key_id: XXXXXXXXXXXXXXXXXXXXXX
  secret_access_key: XXXXXXXXXXXXXXXXXXXXXX
  decoders:
  - {type: gzip}
  parser:
    type: fluent-s3-log
    columns:
      - {name: hostname, type: string}
      - {name: dstat, type: string}
filters:
  - type: expand_json
    json_column_name: dstat
    root: "$."
    expanded_columns:
      - {name: "['total cpu usage'].usr", type: double}
      - {name: "['total cpu usage'].sys", type: double}
      - {name: "['total cpu usage'].idl", type: double}
      - {name: "['total cpu usage'].wai", type: double}
      - {name: "['total cpu usage'].hiq", type: double}
      - {name: "['total cpu usage'].siq", type: double}
      - {name: "['memory usage'].used", type: double}
      - {name: "['memory usage'].buff", type: double}
      - {name: "['memory usage'].cach", type: double}
      - {name: "['memory usage'].free", type: double}
      - {name: "['load avg'].1m", type: double}
      - {name: "['load avg'].5m", type: double}
      - {name: "['load avg'].15m", type: double}
      - {name: "['net/total'].recv", type: double}
      - {name: "['net/total'].send", type: double}
  - type: column
    columns:
      - {name: time}
      - {name: key}
      - {name: hostname}
      - {name: cpu_usr, src: "['total cpu usage'].usr"}
      - {name: cpu_sys, src: "['total cpu usage'].sys"}
      - {name: cpu_idl, src: "['total cpu usage'].idl"}
      - {name: cpu_wai, src: "['total cpu usage'].wai"}
      - {name: cpu_hiq, src: "['total cpu usage'].hiq"}
      - {name: cpu_siq, src: "['total cpu usage'].siq"}
      - {name: mem_used, src: "['memory usage'].used"}
      - {name: mem_buff, src: "['memory usage'].buff"}
      - {name: mem_cach, src: "['memory usage'].cach"}
      - {name: mem_free, src: "['memory usage'].free"}
      - {name: load_1m, src: "['load avg'].1m"}
      - {name: load_5m, src: "['load avg'].5m"}
      - {name: load_15m, src: "['load avg'].15m"}
      - {name: net_recv, src: "['net/total'].recv"}
      - {name: net_send, src: "['net/total'].send"}
out:
  type: stdout

out の設定

outMySQLなので、MySQLでテーブルを作成して、設定したら終わりです。

github.com

以下が最終形態の config.yml です。

in:
  type: s3
  bucket: my-logs
  path_prefix: web/
  access_key_id: XXXXXXXXXXXXXXXXXXXXXX
  secret_access_key: XXXXXXXXXXXXXXXXXXXXXX
  decoders:
  - {type: gzip}
  parser:
    type: fluent-s3-log
    columns:
      - {name: hostname, type: string}
      - {name: dstat, type: string}
filters:
  - type: expand_json
    json_column_name: dstat
    root: "$."
    expanded_columns:
      - {name: "['total cpu usage'].usr", type: double}
      - {name: "['total cpu usage'].sys", type: double}
      - {name: "['total cpu usage'].idl", type: double}
      - {name: "['total cpu usage'].wai", type: double}
      - {name: "['total cpu usage'].hiq", type: double}
      - {name: "['total cpu usage'].siq", type: double}
      - {name: "['memory usage'].used", type: double}
      - {name: "['memory usage'].buff", type: double}
      - {name: "['memory usage'].cach", type: double}
      - {name: "['memory usage'].free", type: double}
      - {name: "['load avg'].1m", type: double}
      - {name: "['load avg'].5m", type: double}
      - {name: "['load avg'].15m", type: double}
      - {name: "['net/total'].recv", type: double}
      - {name: "['net/total'].send", type: double}
  - type: column
    columns:
      - {name: time}
      - {name: key}
      - {name: hostname}
      - {name: cpu_usr, src: "['total cpu usage'].usr"}
      - {name: cpu_sys, src: "['total cpu usage'].sys"}
      - {name: cpu_idl, src: "['total cpu usage'].idl"}
      - {name: cpu_wai, src: "['total cpu usage'].wai"}
      - {name: cpu_hiq, src: "['total cpu usage'].hiq"}
      - {name: cpu_siq, src: "['total cpu usage'].siq"}
      - {name: mem_used, src: "['memory usage'].used"}
      - {name: mem_buff, src: "['memory usage'].buff"}
      - {name: mem_cach, src: "['memory usage'].cach"}
      - {name: mem_free, src: "['memory usage'].free"}
      - {name: load_1m, src: "['load avg'].1m"}
      - {name: load_5m, src: "['load avg'].5m"}
      - {name: load_15m, src: "['load avg'].15m"}
      - {name: net_recv, src: "['net/total'].recv"}
      - {name: net_send, src: "['net/total'].send"}
out:
  type: mysql
  host: localhost
  user: username
  password: userpassword
  database: my_s3_logs
  table: dstat
  mode: insert

実行

$ embulk run config.yml

約80万レコードをS3からの取得から開始して数十分でMySQLに投入できました。これは早い(自分で書かなくてよかった。。。)。

Embulk便利!!

プラグインも今あるもので大抵のことは実現出来そうです。

これからは積極的使っていこうと思います。

今日のCakePHP3

  • 初めてのfind()
  • logQuery(true) 良い。
<?php
    $conn = \Cake\Datasource\ConnectionManager::get('test');
    $conn->logQueries(true);
  • namespaceによるいい感じのクラスの差し替え良い。holiday_jp-php を使って、Timeクラスに isHoliday() メソッドを生やすなどした。
  • Collection良い。
    • HtmlHelper::tableCells() とかと合わせても活用できそう。
  • 早速、Validatorを拡張したくなっているのだけれども、どうしたものか。

遅れに遅れて今更CakePHP3を触りはじめた

まだまだ、全然わかっていない状況です。

社内では既にいろいろと地雷は踏み潰され、さらにPluginもいろいろできている状況からの最後尾スタート。

とりあえず、今日の感想は

  • ビルドインサーバ最高。Vagrantがいらない。Mac上でカジュアルに開発できるの面白い。
  • MigrationsはPhinxを既につかっていたので、面白みがない。使っていなければよかった(感動的な意味で)。
  • ルーティングされるURLが今までと違って焦った。DashedRoute がデフォルトで「なんだこのルーティング???」となってた(CakePHP2っぽいのは InflectedRoute)。ビルドインサーバのせいだと思って「ビルドインサーバ使えないのか。。」とか落ち込んでたらビルドインサーバのせいではなかった。

これから頑張る。

github.com

AWSなコマンドラインツールをつくるときにAWS CLIと同じようにAWSアクセスキーやリージョン情報を取得できるライブラリ awsecrets

を作って、awspeckumomeで使っています。

github.com

使い方

例えば、EC2インスタンスの情報をただ取得するコマンドラインツールを作るとき Awsecrets.load メソッドを利用して

#!/usr/bin/env ruby
require 'awsecrets'
Awsecrets.load
ec2_client = Aws::EC2::Client.new
puts ec2_client.describe_instances({ instance_ids: [ARGV.first] }).reservations.first.instances.first

というコマンドラインツール ec2sample を書いたら、

$ ec2sample i-1aa1aaaa --profile mycreds --region ap-northeast-1

$ AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX AWS_REGION=ap-northeast-1 ec2sample i-1aa1aaaa

$ cat <<EOF > secrets.yml
region: ap-northeast-1
aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
EOF
$ ec2sample i-1aa1aaaa

など、AWS CLIと同じような感じでAWSアクセスキーやリージョンを取得して Aws::config にセットしていい感じに aws-sdk を活用できます。中身はaws_config.gemとaws-sdk.gemでできています。

使いドコロは難しいですが、RubyAWSコマンドラインツールをつくるときには是非ご検討ください。

AWS CloudWatchのメトリクスをCLIからカジュアルに確認できるツールkumomeを作った

AWSのCloudWatchのメトリクスをCLIからカジュアルに確認したいと思ってツールを作り始めている - Copy/Cut/Paste/Hatena

の続きです。kumome という名前でリリースしました。

github.com

asciicast

これは何?

AWS CloudWatchのメトリクスをCLIから確認するためのツールです。特徴としてコマンドオプションをYAMLファイルで拡張できます。

インストールは

$ gem install kumome

デフォルトの設定では ec2 rds elb のメトリクスを取得可能になっています。

$ kumome --ec2=i-123ab45c,i-890ed12c --rds=my-rds --elb=my-elb --profile mycreds

デフォルトの設定は以下。kumome config で現在の設定を出力可能です。

---
resources:
  ec2:
    namespace: AWS/EC2 # required
    dimensions_name: InstanceId # required
    metrics:
      cpu:
        metric_name: CPUUtilization # required
        statistic: Average # required
        unit: Percent
        alarm: '>=50' # "metric alarm name" or "operator and number"
      netin:
        metric_name: NetworkIn
        statistic: Average
        unit: Bytes
      netout:
        metric_name: NetworkOut
        statistic: Average
        unit: Bytes
  rds:
    namespace: AWS/RDS
    dimensions_name: DBInstanceIdentifier
    metrics:
      cpu:
        metric_name: CPUUtilization
        statistic: Average
        unit: Percent
        alarm: '>=50'
      read:
        metric_name: ReadIOPS
        statistic: Average
        unit: Count/Second
      write:
        metric_name: WriteIOPS
        statistic: Average
      conn:
        metric_name: DatabaseConnections
        statistic: Average
        unit: Count
        alarm: '>=50'
  elb:
    namespace: AWS/ELB
    dimensions_name: LoadBalancerName
    metrics:
      req:
        metric_name: RequestCount
        statistic: Sum
      400:
        metric_name: HTTPCode_Backend_4XX
        statistic: Sum
      500:
        metric_name: HTTPCode_Backend_5XX
        statistic: Sum

例えばAWS Lambdaのメトリクスを取得したいときには、以下のようなコンフィグを記述します。

---
resources:
  lambda: # "command option name"
    namespace: AWS/Lambda # required
    dimensions_name: FunctionName # required
    metrics:
      count:
        metric_name: Invocations # required
        statistic: Sum # required
        unit: Count
        alarm: '>=100' # "metric alarm name" or "operator and number"
      error:
        metric_name: Errors
        statistic: Sum
        unit: Count
        alarm: '>=5'
      duration:
        metric_name: Duration
        statistic: Average

そうすれば新たに ---lambda オプションをつくることができます。

$ kumome --config=./custom.yml --lambda=my-lambda-func-name,hook-lambda-func-name --profile mycreds

カスタムメトリクス用のコマンドオプションも作り放題です。

というわけで

自分は、プロジェクトごとにconfig.ymlを作成しておいて、何かあったときは、Amazon Management Consoleにログインして確認するのではなく、まず kumome でざっと確認するようにしています。

カジュアルに確認できるので、心理的に良いです。

まだ、作り始めたばかりですので機能も少ないですが是非お試し下さい。 Pull Requestお待ちしています。