日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

PHPのjson_encodeで空オブジェクトを出力する

phpで空オブジェクトを出力したくなって、
おもむろに json_encode([]); とやったら空配列になってしまいました。

$php -a
Interactive shell

php > echo json_encode([]);
[]

さてどうしたものかと思ってふと浮かんだstdClassを突っ込んでみました。
そしたら空オブジェクトがおもむろに表示されました。

php > echo json_encode(new stdClass());
{}

もしかしてオプションある?と思って調べてみたらありました。

http://php.net/manual/ja/json.constants.php

php > echo json_encode([], JSON_FORCE_OBJECT);
{}

php最高!めでたしめでたし。

monologで例外のstack traceをいい感じに表示する

普段phpでログを出力するのにmonologを使っていて、
開発時にはdocker-composeでサーバを起動してログを標準出力に出したりしています。
開発時に例外が発生したタイミングでmonolog経由でエラーログを出しているのですが、
stack traceが1行に表示されてしまって見づらかったのでなにかいい方法はないかと調べてみました。
検証に使ったmonologのversionは 1.23.0 です。

monolog

github.com

そしたらこのstack overflowにたどり着いて、なにかのPRにたどり着きました。

stackoverflow.com

github.com

PRのコメントをみたらincludeStacktraces()を呼ぶといいらしいとのことでした。
LineFormatterクラスのメソッドらしいですね。

monolog/LineFormatter.php at 1.23.0 · Seldaek/monolog · GitHub

雑に検証してみました。
コードはこちらです。綺麗さのかけらもないです。
もちろんですが composer install の実行が必要です。
3段階くらい例外がネストしているケースを表示してみました。

monologで例外のstack traceをいい感じに表示する · GitHub

<?php

require_once 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;

$log1 = new Logger('いい感じにみえないやつ');
$log1->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));

$log2 = new Logger('いい感じにみえるやつ');
$handler = new StreamHandler('php://stdout', Logger::DEBUG);
$formatter = new LineFormatter();
$formatter->includeStacktraces(true);
$log2->pushHandler($handler->setFormatter($formatter));

function a() {
    throw new \Exception('aaa');
}

function b() {
    try {
        a();
    } catch (\Exception $e) {
        throw new \Exception('bbb', 0, $e);
    }
}

function c() {
    try {
        b();
    } catch (\Exception $e) {
        throw new \Exception('ccc', 0, $e);
    }
}

try {
    c();
} catch (\Exception $e) {
    $log1->error($e);
    $log2->error($e);
}

この状態で実行すると下記のような感じになります。
includeStacktracesを設定していないとstack traceが全て1行に出てしまいますが、
設定していると各行にいい感じに出ているかと思います。
便利。

$php main.php
[2018-03-16 21:23:20] いい感じにみえないやつ.ERROR: Exception: aaa in /Users/a-tanaka/monologphp/main.php:19 Stack trace: #0 /Users/a-tanaka/monologphp/main.php(24): a() #1 /Users/a-tanaka/monologphp/main.php(32): b() #2 /Users/a-tanaka/monologphp/main.php(39): c() #3 {main}  Next Exception: bbb in /Users/a-tanaka/monologphp/main.php:26 Stack trace: #0 /Users/a-tanaka/monologphp/main.php(32): b() #1 /Users/a-tanaka/monologphp/main.php(39): c() #2 {main}  Next Exception: ccc in /Users/a-tanaka/monologphp/main.php:34 Stack trace: #0 /Users/a-tanaka/monologphp/main.php(39): c() #1 {main} [] []
[2018-03-16 21:23:20] いい感じにみえるやつ.ERROR: Exception: aaa in /Users/a-tanaka/monologphp/main.php:19
Stack trace:
#0 /Users/a-tanaka/monologphp/main.php(24): a()
#1 /Users/a-tanaka/monologphp/main.php(32): b()
#2 /Users/a-tanaka/monologphp/main.php(39): c()
#3 {main}

Next Exception: bbb in /Users/a-tanaka/monologphp/main.php:26
Stack trace:
#0 /Users/a-tanaka/monologphp/main.php(32): b()
#1 /Users/a-tanaka/monologphp/main.php(39): c()
#2 {main}

Next Exception: ccc in /Users/a-tanaka/monologphp/main.php:34
Stack trace:
#0 /Users/a-tanaka/monologphp/main.php(39): c()
#1 {main} [] []

まとめ

  • monologのLineFormatterで例外のstack trace表示したいときはincludeStacktracesを設定するといいっぽい

ISUCON7で予選落ちしてきた

去年同様に id:hoto17296id:jp_taku2 と一緒にDarkというチームで出てきました。
結果は26772点!

f:id:arata3da4:20171023182952p:plain

去年は0点で、26722 / 0 = ∞(?)倍のスコアなのでこれこそ圧倒的成長ですね!

arata.hatenadiary.com

最終的なランキングも出てましたが、144(=12 ^2)位でした。
きりがいい。
全ランキングが出ると、来年はもっと上位目指すぞという気持ちになっていいですね。
優勝目指すぞ!ってなって、負けたとしても去年よりはあがった!とかわかるととても嬉しいです。

isucon.net

一週間夏休みを取っていたせいで、一番の懸念は当日の朝起きれるかでしたが起きれて良かったです。
(開始時刻遅くなっちゃったけど
開始時刻が遅れたので少しゆっくり目に行ったらインクの塗り合いが行われてたのがハイライトです。

やったこと

Appサーバ2台、DBサーバ1台の構成で、言語はPHPを選びました。

当日はprivateにしていましたが、ここのレポジトリを使ってコードを置いたりしていました。

github.com

あとは30分毎に今どんな状況だっけみたいな話をしたり誰がどこやってるかみたいなのを整理したりしました。

f:id:arata3da4:20171021211437j:plain

最後の1時間にsplatoonのラスト1分のBGMを流すとめちゃくちゃ焦ってよかったです(?)

www.youtube.com

f:id:arata3da4:20171024191434j:plain

便利だったこと

去年もやったんですが、Makefileによくやる作業をまとめるの便利です。

application server用のMakefile

nginx_log=/tmp/nginx-access.log

alp:
  sudo cat $(nginx_log)|alp --sum --aggregates "/icons/.+"

alp/less:
  sudo cat $(nginx_log)|alp --sum --aggregates "/icons/.+"|less -Ss

restart/php:
  sudo systemctl restart isubata.php.service

clean/nginx:
  sudo rm -f $(nginx_log)
  $(MAKE) restart/nginx

restart/nginx:
  sudo systemctl restart nginx.service

db server用のMakefile

slow_query=/var/log/mysql/mysql-slow.log

slow/ptquery:
  sudo pt-query-digest $(slow_query)

clean/slow:
  sudo rm -f $(slow_query)
  sudo systemctl restart mysql.service

mysql:
  mysql -uisucon -pisucon isubata

例えばnginxのログを一度削除して、nginxを再起動したいみたいなコマンドを作ったりしました。
ログを削除するときにログの場所どこだっけとか、
systemctlのコマンドこれであってるっけとか考えなくて良くて、
make clean/nginx でログを削除して、nginxをリスタートできます。
便利!
去年は色々書いてたけど、今年はあまり書かなかったので、みんなあんまり使ってなかったのが悲しかったです。
コマンドが1コマンドが多いから特にいらんかったみたいな話もありました。
来年はもうちょっと色々書いて便利にしていきたい。

ベンチマーカーも公開されたので、復習していこうと思います。
運営の方々当日大変だったと思いますがとても楽しめました!
ありがとうございました!

github.com

PHPのプロファイリングが出来るblackfireを試してみた

またもやISUCONで使おうと思ってblackfireをいうものを試してみました。
有料な機能も結構ありますが、
無料で使える機能でもどのメソッドがどれくらいの回数呼ばれてるのかとか、
どれくらいの割合を使っているのかとかがわかって便利でした。

blackfire.io

今回検証に利用したコードはここに置きました。

github.com

blackfireに既に登録済みで、server id, server tokenが手に入っていて、
docker-composeとGNU makeが入ってれば make compose id={server id} token={server token} で動かせるはずです。

やること

  • blackfireに登録してserver id, server tokenを取得
  • サーバにblackfire agentのインストール
  • blackfireのchrome extensionをインストール
  • プロファイリングする

という感じです。

1. blackfireに登録してserver id, server tokenを取得

しましょう!
Login - Blackfire

2. サーバにblackfire agentのインストール

この辺をやるだけです。

Installation - Blackfire

Dockerで作成した時のログはこんな感じでした。

# Configuring the Debian Repository

Step 4/10 : RUN wget -O - https://packagecloud.io/gpg.key | apt-key add -
 ---> Running in ee7d25eb98d1
converted 'https://packagecloud.io/gpg.key' (ANSI_X3.4-1968) -> 'https://packagecloud.io/gpg.key' (UTF-8)
--2017-10-15 16:41:40--  https://packagecloud.io/gpg.key
Resolving packagecloud.io (packagecloud.io)... 50.97.198.58, 2607:f0d0:2101:270::2
Connecting to packagecloud.io (packagecloud.io)|50.97.198.58|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3889 (3.8K) [application/octet-stream]
Saving to: 'STDOUT'

     0K ...                                                   100% 53.7M=0s

2017-10-15 16:41:40 (53.7 MB/s) - written to stdout [3889/3889]

OK
 ---> 032abc5bc6fb
Removing intermediate container ee7d25eb98d1
Step 5/10 : RUN echo "deb http://packages.blackfire.io/debian any main" | tee /etc/apt/sources.list.d/blackfire.list
 ---> Running in be9b5a302725
deb http://packages.blackfire.io/debian any main
 ---> 0437a3abc6ce
Removing intermediate container be9b5a302725
Step 6/10 : RUN apt-get update
 ---> Running in 44f15f9f8197
Hit http://security.debian.org jessie/updates InRelease
Ign http://deb.debian.org jessie InRelease
Get:1 http://security.debian.org jessie/updates/main amd64 Packages [547 kB]
Hit http://deb.debian.org jessie-updates InRelease
Get:2 http://packages.blackfire.io any InRelease [23.2 kB]
Hit http://deb.debian.org jessie Release.gpg
Get:3 http://deb.debian.org jessie-updates/main amd64 Packages [23.1 kB]
Hit http://deb.debian.org jessie Release
Get:4 http://deb.debian.org jessie/main amd64 Packages [9063 kB]
Get:5 http://packages.blackfire.io any/main amd64 Packages [1138 B]
Fetched 9658 kB in 4s (2006 kB/s)
Reading package lists...
 ---> 7bb05b5d43cc
Removing intermediate container 44f15f9f8197

# Configuring the Debian Repository -- ここまで
# Installing the Agent

Step 7/10 : RUN apt-get install -y blackfire-agent
 ---> Running in 792b428f7e45
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
  blackfire-agent
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 6004 kB of archives.
After this operation, 16.4 MB of additional disk space will be used.
Get:1 http://packages.blackfire.io/debian/ any/main blackfire-agent amd64 1.14.1 [6004 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 6004 kB in 22s (263 kB/s)
Selecting previously unselected package blackfire-agent.
(Reading database ... 12926 files and directories currently installed.)
Preparing to unpack .../blackfire-agent_1.14.1_amd64.deb ...
Unpacking blackfire-agent (1.14.1) ...
Processing triggers for systemd (215-17+deb8u7) ...
Setting up blackfire-agent (1.14.1) ...
creating blackfire system user


    ADDITIONAL STEP REQUIRED!

    If this is a new install please run:

    sudo blackfire-agent --register
    sudo /etc/init.d/blackfire-agent start

 ---> 3129728f20d6
Removing intermediate container 792b428f7e45

# sudo blackfire-agent -register
# 上記コマンドを実行するとインタラクティブにserver id, server tokenを設定してくれます。
# しかし、最終的には /etc/blackfire/agent に書き込まれるのでここではserver id, tokenを設定したファイルを置いています。

Step 8/10 : COPY ./blackfire.ini /etc/blackfire/agent
 ---> fb9e504cb609
Step 9/10 : RUN /etc/init.d/blackfire-agent restart
 ---> Running in dea658865681
Restarting Blackfire Agent: blackfire-agent.
 ---> 58a923e72362
Removing intermediate container dea658865681

# Installing the Agent  -- ここまで
# Installing the Blackfire CLI tool は飛ばします
# Installing the PHP Probe

Step 10/10 : RUN apt-get install blackfire-php
 ---> Running in 1dc806414984
Reading package lists...
Building dependency tree...
Reading state information...
Suggested packages:
  php5-common php5-cli php7.1-cli php7.1-common
The following NEW packages will be installed:
  blackfire-php
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 2006 kB of archives.
After this operation, 5532 kB of additional disk space will be used.
Get:1 http://packages.blackfire.io/debian/ any/main blackfire-php amd64 1.18.0 [2006 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 2006 kB in 9s (214 kB/s)
Selecting previously unselected package blackfire-php.
(Reading database ... 12938 files and directories currently installed.)
Preparing to unpack .../blackfire-php_1.18.0_amd64.deb ...
Unpacking blackfire-php (1.18.0) ...
Setting up blackfire-php (1.18.0) ...
blackfire-php install completed.

# Installing the PHP Probe -- ここまで
# これをやれば php.ini 周りの設定にblackfireの設定が追加されます。
# なので php -i | grep blackfireをやった時にblackfireが設定されていることがわかるはずです。
# されていない場合は別のバージョンのphpにインストールされている可能性があるので
# extension=blackfire.so とか設定に追加すればもしかしたら動くかも・・・w

$ php -i |grep blackfire
Additional .ini files parsed => /usr/local/etc/php/conf.d/zz-blackfire.ini
    with blackfire v1.18.0~linux-x64-non_zts71, https://blackfire.io, by SensioLabs
blackfire
blackfire.agent_socket => unix:///var/run/blackfire/agent.sock => unix:///var/run/blackfire/agent.sock
blackfire.agent_timeout => 0.25 => 0.25
blackfire.env_id => no value => no value
blackfire.env_token => no value => no value
blackfire.log_file => no value => no value
blackfire.log_level => 1 => 1
blackfire.server_id => no value => no value
blackfire.server_token => no value => no value

3. blackfireのchrome extensionをインストール

blackfire cliを使えばなんか出来るっぽいですが、
とりあえずchrome拡張を入れて動かした感じです。

Google Chrome - Blackfire

4. プロファイリングする

プロファイリングしたいページに行って、
chrome拡張を利用するとこんな感じになります。

f:id:arata3da4:20171016021409p:plain

f:id:arata3da4:20171016021418p:plain

f:id:arata3da4:20171016021427p:plain

そして View Call Graph を押すと下記のようなページが出るはずです。
なんか便利そう!

f:id:arata3da4:20171016021538p:plain

感想

  • なんか便利そうなので仕事でも使っていこうかなと思いました(小並感