ジョギング
- 香椎浜 x 2
日課
- (腕立て x 30 + 腹筋 x 30) x 3
天気
イマイチ。雨が降るんだか、降らないんだか。
奥さん
ほんと苦労が絶えない感じ。なんとか力になってあげたいところではあるが、今のところでは打てる手は打ち尽くしてしまったので、見守るしかないのかなと思っている。
頑張りすぎないで欲しい。
awspec の CloudWatch Logs Resource Type の Generator を作ろうとしています。
一応、Pull request 済みです。
Log group をキーとして Metric Filter と Subscription Filter を取得するメソッドを以下のように書いていました。
def generate_log_metric_filters_specs(log_group) req = { log_group_name: log_group } metric_filters = [] loop do res = cloudwatch_logs_client.describe_metric_filters(req) metric_filters.push(*res.metric_filters) break if res.next_token.nil? req[:next_token] = res.next_token end metric_filter_lines = [] metric_filters.each do |metric_filter| line = "it { should have_metric_filter('#{metric_filter.filter_name}') }" metric_filter_lines.push(line) end metric_filter_lines end def generate_log_subscription_filters_specs(log_group) req = { log_group_name: log_group } subscription_filters = [] loop do res = cloudwatch_logs_client.describe_subscription_filters(req) subscription_filters.push(*res.subscription_filters) break if res.next_token.nil? req[:next_token] = res.next_token end subscription_filter_lines = [] subscription_filters.each do |subscription_filter| line = "it { should have_subscription_filter('#{subscription_filter.filter_name}')" unless subscription_filter.filter_pattern.empty? line += ".filter_pattern('#{subscription_filter.filter_pattern}')" end line += ' }' subscription_filter_lines.push(line) end subscription_filter_lines end
なんと無く冗長です。
インフラでは冗長化は当たり前の事かもしれませんが、プログラミングでは冗長はあまり好まれないと考えています。
ということで、個人的には以下の部分が冗長だと思っています。
... req = { log_group_name: log_group } metric_filters = [] loop do res = cloudwatch_logs_client.describe_metric_filters(req) metric_filters.push(*res.metric_filters) break if res.next_token.nil? req[:next_token] = res.next_token end ... req = { log_group_name: log_group } subscription_filters = [] loop do res = cloudwatch_logs_client.describe_subscription_filters(req) subscription_filters.push(*res.subscription_filters) break if res.next_token.nil? req[:next_token] = res.next_token end ...
これらの処理をうまーく、一つの処理にまとめられればメソッドの行数は減らせて冗長を解消出来そうです。
以下はサンプル実装です。
require 'aws-sdk' require 'pp' cloudwatch_logs_client = Aws::CloudWatchLogs::Client.new(profile: 'your-profile', region: 'ap-northeast-1') filter_types = %w(metric subscription) filter_types.each do |type| define_method 'select_all_cloudwatch_logs_' + type + '_filter' do |*args| req = { log_group_name: args.first } method_name = 'describe_' + type + '_filters' resources = [] loop do res = cloudwatch_logs_client.method(method_name).call(req) case type when 'metric' then resources.push(*res.metric_filters) when 'subscription' then resources.push(*res.subscription_filters) end break if res.next_token.nil? req[:next_token] = res.next_token end resources end end pp select_all_cloudwatch_logs_metric_filter('my_log_group_name') pp select_all_cloudwatch_logs_subscription_filter('my_log_group_name')
ポイントは define_method
ですな。実行すると以下のようなメソッドに展開されると妄想しています。
def select_all_cloudwatch_logs_metric_filter(log_group) req = { log_group_name: log_group } metric_filters = [] loop do res = cloudwatch_logs_client.describe_metric_filters(req) metric_filters.push(*res.metric_filters) break if res.next_token.nil? req[:next_token] = res.next_token end end ... def select_all_cloudwatch_logs_subscription_filter(log_group) req = { log_group_name: log_group } subscription_filters = [] loop do res = cloudwatch_logs_client.describe_subscription_filters(req) subscription_filters.push(*res.subscription_filters) break if res.next_token.nil? req[:next_token] = res.next_token end end
実際に実行してみると以下のように出力されました。
$ bundle exec ruby test.rb [#<struct Aws::CloudWatchLogs::Types::MetricFilter filter_name="my_metric_filter", filter_pattern="OK", metric_transformations= [#<struct Aws::CloudWatchLogs::Types::MetricTransformation metric_name="OK_Test", metric_namespace="LogMetrics", metric_value="1", default_value=nil>], creation_time=1491118453323, log_group_name="my_log_group_name">] [#<struct Aws::CloudWatchLogs::Types::SubscriptionFilter filter_name="my_subscription_filter", log_group_name="my_log_group_name", filter_pattern="", destination_arn= "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:elb_log_to_amazones", role_arn=nil, distribution="ByLogStream", creation_time=1491092185825>]
意図した通りの結果が返ってきています。
メソッドの動的生成は初めての体験でした。
define_method
を適材適所で使うことで柔軟で且つ少ないコードで処理を実装出来るって素晴らしいですね。
Ruby すごなあ。
を弄っています。
ドキュメント には、以下のように…
DATADOG_PATCH_MODULES=module:patch,module:patch… e.g. boto:true,redis:false : override the modules patched for this execution of the program (default: none)
ソースコードには…
... # Default set of modules to automatically patch or not PATCH_MODULES = { 'boto': False, 'botocore': False, 'bottle': False, 'cassandra': True, ...
そうなんです、AWS をお使いの方なら馴染み深い boto
や botocore
の文字が踊っています。
dd-trace-py は boto
や botocore
にもパッチを当てるようにして、リクエストに要した処理時間等のデータを Datadog APM に送信する事が出来るようです。
今回は botocore を題材として取り上げてみたいと思います。
botocore とは文字通り、我々が愛して止まない boto3 の core となっているモジュールで、boto3 よりも低いレベルの API 操作を行うモジュールです(という認識です)ので、同じことを boto3 と botocore で実装したい場合、若干ですが書き方が異なってくるようです。
例えば、EC2 を操作出来るポリシーが付与された IAM Role を持つ EC2 上で boto3 を使って describe instances を実行したい場合…
import boto3 client = boto3.client('ec2') response = client.describe_instances() print response.__class__
botocore で書こうとすると以下のようになりました。
import botocore.session session = botocore.session.get_session() client = session.create_client('ec2') response = client.describe_instances() print response.__class__
認証部分が boto3 から botocore の botocore.session.get_session() を呼んでいたりするのかなと妄想しています。ということで、普段 boto3 を使っていれば、ほとんど意識することは無いと思っていますので、あーこんなんのが boto3 の裏で暗躍しているんだという位に留めておきます。
上記の boto3 を利用したスクリプトで ddtrace-run で describe instances を追跡してみたいと思います。
$ python trace-demo-botocore.py <type 'dict'>
普通に実行すると上記のように出力されるだけですが、ddtrace-run と一緒に実行すると…
$ DATADOG_PATCH_MODULES=botocore:true ddtrace-run python trace-demo-botocore.py <type 'dict'>
出力には特に違いは見られませんが、Datadog APM では
Describe Instances の API コールが投げられている事が確認出来ます。
以下のように botocore にパッチを当てて S3 に対して List Buckets を行ってみたいと思います。
import boto3 from ddtrace import Pin, patch patch(botocore=True) client = boto3.client('s3') Pin.override(client, service='s3-demo-access') response = client.list_buckets() print response.__class__
以下のように実行します。
$ python trace-demo-botocore.py <type 'dict'>
出力は特に何も変わりませんが、Datadog APM の画面を見ると…
ちゃんと Trace されていて、List Buckets の API コールが叩かれている事が確認出来ます。
dd-trace-py を利用すれば botocore でも API コールについて可視化出来ることが解りました。AWS の API を叩くようなアプリケーションを実装する場合、どうしてもパフォーマンスが出ないとか、継続的にパフォーマンスを監視したい場合等に仕込んでみると良さそうです。
Sinatra でざっくりと Datadog APM サービスを使ってみました。
Python 版の Trace Agent を触ってみたいと思います。
とりあえず、2 回位に分けて書く予定です。
既に Datadog Agent が動いている状態です。
$ sudo /etc/init.d/datadog-agent info ... ====================== Trace Agent (v 5.12.2) ====================== Pid: 23754 Uptime: 52031 seconds Mem alloc: 1766392 bytes Hostname: i-xxxxxxxxxxxxxxxx Receiver: localhost:8126 API Endpoints: https://trace.agent.datadoghq.com Bytes received (1 min): 0 Traces received (1 min): 0 Spans received (1 min): 0 Bytes sent (1 min): 0 Traces sent (1 min): 0 Stats sent (1 min): 0
Python は以下のバージョンを利用しています。
$ python --version
Python 2.7.13
導入はとっても簡単です。
$ pip install ddtrace
以上です。
dd-trace-py は Python のメジャーどころな Web Application Framework 及び Database 系のモジュールに対応しています。
Database 系のモジュールについては…
Then let’s patch widely used Python libraries:
と書かれているように、モジュールに Patch を当てる感じになるようです。
dd-trace-py を導入すると、一緒に ddtrace-run というコマンドも一緒にインストールされます。
$ ddtrace-run -h Execute the given Python program after configuring it to emit Datadog traces. Append command line arguments to your program as usual. Usage: [ENV_VARS] ddtrace-run <my_program> Available environment variables: DATADOG_ENV : override an application's environment (no default) DATADOG_TRACE_ENABLED=true|false : override the value of tracer.enabled (default: true) DATADOG_TRACE_DEBUG=true|false : override the value of tracer.debug_logging (default: false) DATADOG_PATCH_MODULES=module:patch,module:patch... e.g. boto:true,redis:false : override the modules patched for this execution of the program (default: none) DATADOG_TRACE_AGENT_HOSTNAME=localhost: override the address of the trace agent host that the default tracer will attempt to submit to (default: localhost) DATADOG_TRACE_AGENT_PORT=8126: override the port that the default tracer will submit to (default: 8126) DATADOG_SERVICE_NAME : override the service name to be used for this program (no default) This value is passed through when setting up middleware for web framework integrations. (e.g. pylons, flask, django) For tracing without a web integration, prefer setting the service name in code.
先述のように Patch に対応したモジュールを import した Python プログラムを引数にして実行することで、簡単に Datadog APM 用のメトリクスを送信することが出来ます。
以下のような雑な sqlite にアクセスするスクリプトがあったとします。
import sqlite3 db = sqlite3.connect(":memory:") sql = """ create table users ( id integer, name varchar(10), age integer ); """ db.execute(sql) sql = "insert into users values (1, 'foo', 26)" db.execute(sql) c = db.cursor() c.execute("select * from users where id = 1") for row in c: print row
普通に実行すると以下のように出力されます。
$ python trace-demo-sqlite.py (1, u'foo', 26)
これを ddtrace-run でフックして実行してみます。
$ ddtrace-run python trace-demo-sqlite.py (1, u'foo', 26)
実行結果にはなにも影響は見られませんが、Datadog APM のコンソールを見ると…
発行されたクエリ等の Trace 情報を確認することが出来ます。
コマンドラインで実行するようなスクリプトであれば ddtrace-run を利用すれば良いと思いますが、Web アプリケーション等からライブラリとして利用されるアプリケーションの場合には、モジュールに Patch を当てるような感じで dd-trace-py を利用することが出来ます。
ということで、先述の sqlite にアクセスする雑なスクリプトを以下のように修正しました。
import sqlite3 from ddtrace import Pin, patch patch(sqlite3=True) db = sqlite3.connect(":memory:") sql = """ create table users ( id integer, name varchar(10), age integer ); """ db.execute(sql) sql = "insert into users values (1, 'foo', 26)" db.execute(sql) c = db.cursor() c.execute("select * from users where id = 1") for row in c: print row
下図のように Datadog APM にデータが送信されていることを確認出来ます。
また、接続オブジェクトに任意のメタデータを送信データに含めたい場合には以下のように Pin
を利用することが Service 名を変更することが出来ます。
import sqlite3 from ddtrace import Pin, patch patch(sqlite3=True) db3 = sqlite3.connect(":memory:") Pin.override(db3, service='sqlite2') sql = """ create table users ( id integer, name varchar(10), age integer ); """ db3.execute(sql) sql = "insert into users values (1, 'foo', 26)" db3.execute(sql) c = db3.cursor() c.execute("select * from users where id = 1") for row in c: print row
以下のように Service 名を変更することが出来ました。
ざっくりと dd-trace-py を触ってみました。
コマンドラインで実行出来るスクリプトであれば ddtrace-run を利用することで、簡単に Datadog APM にメトリクスを送信することが出来そうです。
引き続き、Flask を利用した雑なアプリケーションで dd-trace-py を深掘り出来ればと考えています。