Datadog APM について
NewRelic や X-Ray とか Application Insights 等の Application Performance Monitoring なソリューション。お仕事ではとある案件で Datadog APM を実戦投入していて、Datadog APM で監視していた内容でアプリケーションのチューニングに役立てていたりする。(実際に 10 倍くらいの速度改善が見られた)
この Datadog APM を利用する場合には、アプリケーションに SDK を組み込んで利用することになるが、Datadog がオフィシャルに公開している SDK は以下の言語に限られているのが現状。
- https://github.com/DataDog/dd-trace-go
- https://github.com/DataDog/dd-trace-py
- https://github.com/DataDog/dd-trace-rb
しかし、Tracing API が公開されているので Bash からでも利用することが出来るので、シェルスクリプトで作ったアプリケーションのモニタリングも可能になる。
ということで、Tracing API
Tracing API
俺のサンプル
仕組み
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 みたいにプロセス単位でドリルダウンしてモニタリング出来るようになるようだ。
Trace されたデータで見る put するデータの構造
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 として扱いたい場合に指定が必要になる(これが肝だと思う)- 上記に合わせて、1 つの Trace として扱いたい場合には
trace_idを揃えておく必要がある(上記だと1038524275で揃えている) startやdurationについては 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 をしばらく流しておくと以下のようにトレースデータが出力される。

何となくではあるが、プログラムの処理時間を確認することが出来ているつもり。
最後に
dd-trace-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(); ?>