ジョギング
- 大雨の為お休み
頭痛
- 終日頭痛
- 肩が張った感じが続く
日課
- (腕立て x 30 + 腹筋 x 30) x 3
終日
- 雨だった
- こんなに雨が降り続くのは久しぶりだった気がする
NewRelic や X-Ray とか Application Insights 等の Application Performance Monitoring なソリューション。お仕事ではとある案件で Datadog APM を実戦投入していて、Datadog APM で監視していた内容でアプリケーションのチューニングに役立てていたりする。(実際に 10 倍くらいの速度改善が見られた)
この Datadog APM を利用する場合には、アプリケーションに SDK を組み込んで利用することになるが、Datadog がオフィシャルに公開している SDK は以下の言語に限られているのが現状。
しかし、Tracing API が公開されているので Bash からでも利用することが出来るので、シェルスクリプトで作ったアプリケーションのモニタリングも可能になる。
Tracing API は下図ように Datadog Agent に同梱されている Trace Agent(trace-agent) により提供されている。
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 みたいにプロセス単位でドリルダウンしてモニタリング出来るようになるようだ。
sample1.php を何度か実行すると以下のように Datadog にてトレース情報を確認することが出来る。
トレース情報は 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 として扱いたい場合に指定が必要になる(これが肝だと思う)trace_id
を揃えておく必要がある(上記だと 1038524275
で揃えている)start
や duration
については UNIX タイムを送信する必要がある(ナノ秒)※但し、上記 JSON の duration はマイクロ秒だよな...でも問題無さそう生成された 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 をしばらく流しておくと以下のようにトレースデータが出力される。
何となくではあるが、プログラムの処理時間を確認することが出来ているつもり。
以下のように既にサードパーティ製ではあるが、PHP 向けのクライアントライブラリが公開されていた。
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(); ?>
言語を基礎から学ぶ機会が得られて本当に良かった。
ということで、教材の「2. Pythonをはじめよう」で FizzBuzz のサンプルがあったので UnitTest を使ってテストドリブンで FizzBuzz を実装してみた。
今回は以下のような環境で FizzBuzz する。
$ python -V
Python 3.6.2
もはや説明不要の泣く子も黙るゲーム。
以下のようなテストを書く。
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.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
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 関数に実装を加える。
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
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 つ成功したぜ。
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
おお。
改めて 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
テスト成功!!
すごくシンプルだけどテストドリブンが体験出来た。なんか楽しいなあ。
今まで何となくフワッと Python を書いていたので、基礎から学びたくて参加した。
若干駆け足気味だったけど、講師の @shimizukawa さんの解説がとても解りやすくて勉強になったので、何点かメモっておく。
三重クォート知らなかった。#pycamp
— Yohei Kawahara(かっぱ) (@inokara) 2017年9月30日
{'list': '順番がある変更可能な入れ物', 'tuple': '順番がある変更不可能な入れ物', 'dict': '順番が無くて Key Value で表現する入れ物'} #pycamp
— Yohei Kawahara(かっぱ) (@inokara) 2017年9月30日
dict の key は文字列に限らない。 #pycamp
— Yohei Kawahara(かっぱ) (@inokara) 2017年9月30日
PyPI はパイピーアイと読む。初めて知った。#pycamp
— Yohei Kawahara(かっぱ) (@inokara) 2017年9月30日
モジュールの管理は venv が推奨されていることを知った ファイルを開く際にはエンコードは指定する
モジュールをインポートする場合...
import calc と from calc import add どっちがいいの? ↑名前空間を分けるという意味では import calc の方がいいかも ↑若干だけど、個別 import の方が効率が良い
import sys sys.path
モジュール置き場は PYTHONPATH
に指定した方が良いらしい。
懇親会、一軒目はモツ鍋屋さん。福岡に来てあんまりモツ鍋食べてないよなと思いながら、大盛りのニラに膝が震えた。二軒目はクラフトビールを飲ませてくれるいつもお店。美味しいんだけどだいぶん羽目を外して飲みすぎたかもしれぬ。そして、その流れで小山さんとか市川さん、杉本さんとかと話し込んでいるうちに終電を逃してしまったので、タクシーで香椎浜まで市川さんにご一緒させて頂いて、その後は自宅までトボトボと歩いて帰った。
香椎浜で独りおならをすかす。星がとても綺麗。
— Yohei Kawahara(かっぱ) (@inokara) 2017年9月30日
星がとても綺麗だったけど、奥さんに散々怒られた。本当にごめんくさい。