ようへいの日々精進XP

よかろうもん

2017 年 10 月 02 日(月)

ジョギング

  • 大雨の為お休み

頭痛

  • 終日頭痛
  • 肩が張った感じが続く

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

終日

  • 雨だった
  • こんなに雨が降り続くのは久しぶりだった気がする

MacOS XJava をアップデートした

  • JMeter の最新版が Java8 を求めていたので MacOS XJava をアップデートした
  • というか、自分の端末の Java がまだ Java 7 だったことに驚いた
  • Oracle のサイトから JRE だけインストールするとコマンドラインから java -version してもバージョンアップは確認出来なくて...
  • JDK をインストールすれば良かったというオチでした

2017 年 10 月 01 日(日)

ジョギング

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

激辛担々麺

  • ランチに近所の美味しい担々麺屋さんで激辛担々麺を食べる
  • 初めて食べた時には旨さと辛さに悶絶したけど、今日は辛さには悶絶することなくスルスル食べることが出来た
  • だけど、お腹が痛い

夕飯

  • 奥さんの手料理
  • 最近、鍋が多かったので奥さんの手料理でホッとした

Tracing API を PHP で使って少しずつ理解する Datadog APM

Datadog APM について

www.datadoghq.com

NewRelic や X-Ray とか Application Insights 等の Application Performance Monitoring なソリューション。お仕事ではとある案件で Datadog APM を実戦投入していて、Datadog APM で監視していた内容でアプリケーションのチューニングに役立てていたりする。(実際に 10 倍くらいの速度改善が見られた)

この Datadog APM を利用する場合には、アプリケーションに SDK を組み込んで利用することになるが、Datadog がオフィシャルに公開している SDK は以下の言語に限られているのが現状。

しかし、Tracing API が公開されているので Bash からでも利用することが出来るので、シェルスクリプトで作ったアプリケーションのモニタリングも可能になる。

ということで、Tracing API

Tracing API

docs.datadoghq.com

俺のサンプル

github.com

  • dd_trace.php が Tracing API を叩く薄いラッパー
  • sample1.php は Tracing API を利用するアプリケーション (適当に sleep を入れているだけ)

仕組み

Tracing API は下図ように Datadog Agent に同梱されている Trace Agent(trace-agent) により提供されている。

f:id:inokara:20170925000721p:plain

Docker コンテナで動かしている Datadog Agent のプロセスを見てみると以下のようなプロセス起動していて、process-agent と trace-agent が Datadog のEndpoint と通信していることが判る。

root@a830e2803aaf:/var/log/datadog# ss -anp| grep tcp
tcp    LISTEN     0      128                    *:17123                 *:*      users:(("python",pid=13,fd=5))
tcp    LISTEN     0      128            127.0.0.1:6062                  *:*      users:(("process-agent",pid=11,fd=4))
tcp    ESTAB      0      0             172.17.0.6:33460     52.55.150.131:443    users:(("process-agent",pid=11,fd=9))
tcp    ESTAB      0      0             172.17.0.6:55856     54.173.78.245:443    users:(("trace-agent",pid=12,fd=7))
tcp    LISTEN     0      128                   :::8126                 :::*      users:(("trace-agent",pid=12,fd=5))
tcp    LISTEN     0      128                   :::17123                :::*      users:(("python",pid=13,fd=4))

ふむふむ。

ちなみに、process-agent はベータ提供 (??) されている機能の為に利用するエージェントのようで、htop みたいにプロセス単位でドリルダウンしてモニタリング出来るようになるようだ。

docs.datadoghq.com

Trace されたデータで見る put するデータの構造

sample1.php を何度か実行すると以下のように Datadog にてトレース情報を確認することが出来る。

f:id:inokara:20171001155856p:plain

トレース情報は dd_trace.php の以下のメソッドで Datadog に飛ばしている。

...
        public function generateSpandata($span_name, $span_id, $parent_span_id = NULL, $startTime, $durationTime) {
            $data = array(
                'trace_id' => $this->traceId,
                'span_id' => $span_id,
                'name' => $span_name,
                'resource' => $span_name,
                'type' => $this->Type,
                'start' => intval($startTime * 1000000000),
                'duration' => round($durationTime * 1000000000),
            );
            if (! is_null($parent_span_id)) {
                $data['service'] = $this->serviceName . '-' . $span_name;
                $data['parent_id'] = $parent_span_id;
            } else {
                $data['service'] = $this->serviceName;
            }
            return $data;
        }
...

これを実行すると以下のような JSON データが生成されて Datadog に put されることになる。

[
  [
    {
      "trace_id": 1038524275,
      "span_id": 488372325,
      "name": "step_one_sleep", ... 図中の (3)
      "resource": "step_one_sleep",
      "type": "web",
      "start": 1506841510052099800,
      "duration": 1419700146,
      "service": "sample1.php-step_one_sleep",
      "parent_id": 1824231734
    },
    {
      "trace_id": 1038524275,
      "span_id": 569978220,
      "name": "step_two_sleep", ... 図中の (2)
      "resource": "step_two_sleep",
      "type": "web",
      "start": 1506841511471800000,
      "duration": 1753799915,
      "service": "sample1.php-step_two_sleep",
      "parent_id": 1824231734
    },
    {
      "trace_id": 1038524275,
      "span_id": 1824231734,
      "name": "root_sleep", ... 図中の (1)
      "resource": "root_sleep",
      "type": "web",
      "start": 1506841510052099800,
      "duration": 3173500061,
      "service": "sample1.php"
    }
  ]
]
  • parent_id で指定している ID で複数関数の個々の処理時間と合計の処理時間を上図のように 1 つの Trace として扱いたい場合に指定が必要になる(これが肝だと思う)
  • 上記に合わせて、1 つの Trace として扱いたい場合には trace_id を揃えておく必要がある(上記だと 1038524275 で揃えている)
  • startduration については UNIX タイムを送信する必要がある(ナノ秒)※但し、上記 JSON の duration はマイクロ秒だよな...でも問題無さそう

Datadog へのポスト

生成された JSON は HTTP Put メソッドで Datadog Agent に同梱されている Trace Agent に送信する。sample1.php だと以下の部分。

        public function finish($data) {
            echo json_encode([$data]);
            $endpoint = $this->agentEndpoint;
            $curl = curl_init($endpoint);
            $options = array(
                CURLOPT_HTTPHEADER => array(
                    'Content-Type: application/json',
                ),
                CURLOPT_CUSTOMREQUEST => "PUT",
                CURLOPT_POSTFIELDS => json_encode([$data]),
            );
            curl_setopt_array($curl, $options);
            $result = curl_exec($curl);

            return $result;
        }

正常にリクエストが処理された場合にレスポンスは OK が返却されてステータスコード200 を返す。リクエストに異常がある場合には 500 エラーが返却される。

ということで... sample1.php をしばらく流してみると...

以下のように sample1.php をしばらく流しておくと以下のようにトレースデータが出力される。

f:id:inokara:20171001162243p:plain

何となくではあるが、プログラムの処理時間を確認することが出来ているつもり。

最後に

dd-trace-php を使ってみたいかど...

以下のように既にサードパーティ製ではあるが、PHP 向けのクライアントライブラリが公開されていた。

github.com

README を参考にして以下のようなサンプルを作ってみたけど...手元の環境では Datadog Agent にデータが送信されてないので何でだろう...何でだろう...。

<?php

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

use DdTrace\Tracer;

$tracer = Tracer::noop();
$span = $tracer->createRootSpan("sleep_test", "sleep_test", "sleep_test");

var_dump($span);
try {
    $rand_sleep_time = rand(1500000,3000000);
    usleep($rand_sleep_time);
    $span->setMeta("sleep_time", $rand_sleep_time);
} catch (RequestException $e) {
    $span->setError($e);
}

$span->finish();
?>

テストドンブリ(ドリブン)Python FizzBuzz

Python Boot Camp に行ってきた

pyconjp.connpass.com

言語を基礎から学ぶ機会が得られて本当に良かった。

ということで、教材の「2. Pythonをはじめよう」で FizzBuzz のサンプルがあったので UnitTest を使ってテストドリブンで FizzBuzz を実装してみた。

テストドリブン FizzBuzz

ドキュメント

環境

今回は以下のような環境で FizzBuzz する。

$ python -V
Python 3.6.2

FizzBuzz とは

もはや説明不要の泣く子も黙るゲーム。

  • 1 から順に数字を発言する
  • 数字が 3 で割り切れる場合は「Fizz」と発言する
  • 5 で割り切れる場合は「Buzz」と発言する
  • 3 と 5 で割り切れる場合は「FizzBuzz」と発言する

最初のテスト

以下のようなテストを書く。

import unittest


class Testfizzbuzz(unittest.TestCase):

    def test_should_return_normal_number(self):
        self.assertEqual(fizzbuzz(1), "1")

    def test_3_should_return_Fizz(self):
        self.assertEqual(fizzbuzz(3), "Fizz")

    def test_5_should_return_Buzz(self):
        self.assertEqual(fizzbuzz(5), "Buzz")

    def test_3_and_5_should_return_FizzBuzz(self):
        self.assertEqual(fizzbuzz(15), "FizzBuzz")


if __name__ == '__main__':
    unittest.main()

テストを走らせてみる。

$ python fizzbuzz_test.py
EEEE
======================================================================
ERROR: test_3_and_5_should_return_FizzBuzz (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 16, in test_3_and_5_should_return_FizzBuzz
    self.assertEqual(fizzbuzz(15), "FizzBuzz")
NameError: name 'fizzbuzz' is not defined

======================================================================
ERROR: test_3_should_return_Fizz (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 10, in test_3_should_return_Fizz
    self.assertEqual(fizzbuzz(3), "Fizz")
NameError: name 'fizzbuzz' is not defined

======================================================================
ERROR: test_5_should_return_Buzz (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 13, in test_5_should_return_Buzz
    self.assertEqual(fizzbuzz(5), "Buzz")
NameError: name 'fizzbuzz' is not defined

======================================================================
ERROR: test_should_return_normal_number (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 7, in test_should_return_normal_number
    self.assertEqual(fizzbuzz(1), "1")
NameError: name 'fizzbuzz' is not defined

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (errors=4)

そもそも、fizzbuzz という関数が定義されていないのでテストは失敗どころかエラーになってしまう。

ここからはじめてみる。

fizzbuzz 関数の実装(1)

ファイル名を fizzbuzz.py として、以下のように fizzbuzz 関数を実装してみる。

def fizzbuzz(num):
    return str(num)


for num in range(1, 21):
    print(fizzbuzz(num))

実行すると以下のように出力される。

$ python fizzbuzz.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

テスト(2)

fizzbuzz_test.py を以下のように修正して fizzbuzz 関数を import する。

import unittest
from fizzbuzz import fizzbuzz


class Testfizzbuzz(unittest.TestCase):

    def test_should_return_normal_number(self):
        self.assertEqual(fizzbuzz(1), "1")

... 略 ...

if __name__ == '__main__':
    unittest.main()

改めてテストを実行する。

$ python fizzbuzz_test.py
... 略 ...
FFF.
======================================================================
FAIL: test_3_and_5_should_return_FizzBuzz (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 17, in test_3_and_5_should_return_FizzBuzz
    self.assertEqual(fizzbuzz(15), "FizzBuzz")
AssertionError: '15' != 'FizzBuzz'
- 15
+ FizzBuzz

... 略 ...

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=3)

テストが 1 つ成功した。

fizzbuzz 関数の実装(2)

以下のように fizzbuzz 関数に実装を加える。

def fizzbuzz(num):
    if num % 3 == 0 and num % 5 == 0:
        return 'FizzBuzz'
    else:
        return str(num)


for num in range(1, 21):
    print(fizzbuzz(num))

実行すると以下のように出力される。

$ python fizzbuzz.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FizzBuzz
16
17
18
19
20

テスト(3)

fizzbuzz_test.py を実行してテストする。

$ python fizzbuzz_test.py
... 略 ...
.FF.
======================================================================
FAIL: test_3_should_return_Fizz (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 11, in test_3_should_return_Fizz
    self.assertEqual(fizzbuzz(3), "Fizz")
AssertionError: '3' != 'Fizz'
- 3
+ Fizz


======================================================================
FAIL: test_5_should_return_Buzz (__main__.Testfizzbuzz)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "fizzbuzz_test.py", line 14, in test_5_should_return_Buzz
    self.assertEqual(fizzbuzz(5), "Buzz")
AssertionError: '5' != 'Buzz'
- 5
+ Buzz


----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=2)

テストが 2 つ成功したぜ。

fizzbuzz 関数の実装(3)

Fizz と Buzz も追加実装してみる。

def fizzbuzz(num):
    if num % 3 == 0 and num % 5 == 0:
        return 'FizzBuzz'
    elif num % 3 == 0:
        return 'Fizz'
    elif num % 5 == 0:
        return 'Buzz'

    else:
        return str(num)


for num in range(1, 21):
    print(fizzbuzz(num))

実行すると以下のように出力される。

$ python fizzbuzz.py
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

おお。

テスト(4)

改めて fizzbuzz_test.py を実行してテストする。

$ python fizzbuzz_test.py
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

テスト成功!!

以上

すごくシンプルだけどテストドリブンが体験出来た。なんか楽しいなあ。

2017 年 09 月 30 日(土)

ジョギング、日課

  • お休み

Python Boot Camp に参加

pyconjp.connpass.com

今まで何となくフワッと Python を書いていたので、基礎から学びたくて参加した。

若干駆け足気味だったけど、講師の @shimizukawa さんの解説がとても解りやすくて勉強になったので、何点かメモっておく。

  • 三重クォートの使い方、ヒアドキュメントっぽい使い方が出来るんだろうな

  • コレクション周りの理解が深まった

  • モジュールの管理は venv が推奨されていることを知った ファイルを開く際にはエンコードは指定する

  • モジュールをインポートする場合...

import calc と from calc import add どっちがいいの?

↑名前空間を分けるという意味では import calc の方がいいかも
↑若干だけど、個別 import の方が効率が良い
  • モジュールのパスを確認する場合...
import sys
sys.path

モジュール置き場は PYTHONPATH に指定した方が良いらしい。

懇親会からの終電逃し

懇親会、一軒目はモツ鍋屋さん。福岡に来てあんまりモツ鍋食べてないよなと思いながら、大盛りのニラに膝が震えた。二軒目はクラフトビールを飲ませてくれるいつもお店。美味しいんだけどだいぶん羽目を外して飲みすぎたかもしれぬ。そして、その流れで小山さんとか市川さん、杉本さんとかと話し込んでいるうちに終電を逃してしまったので、タクシーで香椎浜まで市川さんにご一緒させて頂いて、その後は自宅までトボトボと歩いて帰った。

星がとても綺麗だったけど、奥さんに散々怒られた。本当にごめんくさい。

2017 年 09 月 29 日(金)

ジョギング

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

奥さん

  • 体調イマイチ
  • 風邪もひいた感じ

夕飯

  • 奥さんに「また、鍋〜?」て言われながらも鍋
  • 野菜も食べれるし鍋大好き

2017 年 09 月 26 日(火)

結局

ジョギング

  • お休み

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

終日

  • 体調悪い
  • 夜なべが出来ない体になってきたなホントに

2017 年 09 月 25 日(月)

[contents]

ジョギング

日課

  • お休み

SRE 本輪読会

www.oreilly.co.jp

福岡界隈で凄いインフラの人たちと一緒に SRE 本のある章をテーマに取り上げつつ、あーでもないこーでもないを 2 時間くらい語り合った。

皆さん、色々と知見を持っていてずっと話を聞いていたいくらい盛り上がったし、面白かった。

また、参加したい。

やらかし

夜中にも関わらず、武川さんを自宅から虎ノ門オフィスまで引っ張り出してしまうくらいのヤバイやらかしだった。

本当に申し訳ない。

人間ってミスる生き物だから、ミスる前提で作業とか設計を考えるべきだと改めて思った。