ようへいの日々精進XP

よかろうもん

2017 年 04 月 06 日(木)

ジョギング

日課

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

天気

イマイチ。雨が降るんだか、降らないんだか。

奥さん

ほんと苦労が絶えない感じ。なんとか力になってあげたいところではあるが、今のところでは打てる手は打ち尽くしてしまったので、見守るしかないのかなと思っている。

頑張りすぎないで欲しい。

2017 年 04 月 04 日(火)

批評

このブログを最も楽しみにしている(自称)奥さんが、最近のブログを読んで一言。

「毎日、同じような内容でつまらない」

すんません。

確かに、判で押したような毎日なのは間違いないが、もう少し奥さんを笑わせられるような書き方をしたい。

パスタ

夕飯当番だったのでパスタを作る。

にんにくの香りやベーコン、魚介の旨味を出し切ることが出来なかった。

反省。

2017 年 04 月 03 日(月)

ジョギング

日課

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

夕焼けが綺麗だった

桜もチラホラと咲き始めているが、何よりも香椎浜は夕焼けが綺麗。ちょうど帰宅してきた奥さんと散歩がてら夕焼けを拝みに行った。

小ネタ道場一本勝負 〜 初体験 Ruby でメソッドの動的生成 〜

なんしよーとや

awspec の CloudWatch Logs Resource Type の Generator を作ろうとしています。

github.com

一応、Pull request 済みです。

github.com

さて、今回

Metric Filter と Subscription Filter を取得するメソッド

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
...

これらの処理をうまーく、一つの処理にまとめられればメソッドの行数は減らせて冗長を解消出来そうです。

ということで、一本

define_method を使ってメソッドを動的に生成することにした

以下はサンプル実装です。

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 すごなあ。

2017 年 04 月 01 日(土)

篠栗九大の森

篠栗の九大の森に奥さんを連れて行った。

「九大の森」という名前の通り、ここは九州大学の研究を目的として整備されているとのこと。

一周 2 キロで適度なアップダウンがあり、クロカン練習に良さそう。

f:id:inokara:20170402131040j:plain f:id:inokara:20170402131053j:plain

ゆっくりと奥さんと話しながら歩いて、森の中に吹く風の匂いとかに癒された。

夕飯

気になっていた宇宙軒の隣のイタリアン、リストランテ・ア・ドマーニ。

ちょっとお高めだったけどパスタがとても美味しかった。

Datadog APM の Python 用クライアント dd-trace-py をざっくりチュートリアルする 〜 botocore 編〜

引き続き

github.com

を弄っています。

ソースコードやドキュメントの端々に

ドキュメント には、以下のように…

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 をお使いの方なら馴染み深い botobotocore の文字が踊っています。

dd-trace-py は botobotocore にもパッチを当てるようにして、リクエストに要した処理時間等のデータを Datadog APM送信する事が出来るようです。

botocore とは

今回は botocore を題材として取り上げてみたいと思います。

github.com

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 の裏で暗躍しているんだという位に留めておきます。

ということで

ddtrace-run x botocore

上記の 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 では

f:id:inokara:20170401091540p:plain

Describe Instances の API コールが投げられている事が確認出来ます。

Patch

以下のように 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 の画面を見ると…

f:id:inokara:20170401092732p:plain

ちゃんと Trace されていて、List Buckets の API コールが叩かれている事が確認出来ます。

ということで

dd-trace-py を利用すれば botocore でも API コールについて可視化出来ることが解りました。AWSAPI を叩くようなアプリケーションを実装する場合、どうしてもパフォーマンスが出ないとか、継続的にパフォーマンスを監視したい場合等に仕込んでみると良さそうです。

2017 年 03 月 31 日(金)

休み

仕事のお休みを頂いて、お義母さんの病院に付き添う予定だったが、土壇場でキャンセルを食らったのでぽっかり一日開く状態になった。

ま、いいや。

ジョギング

日課

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

夕飯

奥さんと博多で待ち合わせ小洒落た中華レストラン。

人柄の良さそうなオーナー兼料理長とイチイチお洒落な盛り付けと絶妙な味付けでリピーターになりそう。中でも担々麺は生まれて初めて、めっちゃ旨いとと思える担々麺を食べた気がした。

餃子とシュウマイが美味しいと評判らしいが、残念ながら仕入れの関係で味わうことが出来なかった。次回は是非食べてみたい。

Datadog APM の Python 用クライアント dd-trace-py をざっくりチュートリアルする 〜 ddtrace-run と Patch 編〜

前回は

Sinatra でざっくりと Datadog APM サービスを使ってみました。

inokara.hateblo.jp

今回は

Python 版の Trace Agent を触ってみたいと思います。

github.com

とりあえず、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

dd-trace-py

導入

導入はとっても簡単です。

$ pip install ddtrace

以上です。

実装

dd-trace-py は Python のメジャーどころな Web Application Framework 及び Database 系のモジュールに対応しています。

Database 系のモジュールについては…

Then let’s patch widely used Python libraries:

と書かれているように、モジュールに Patch を当てる感じになるようです。

コマンドラインツール 〜 ddtrace-run 〜

ddtrace-run

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 用のメトリクスを送信することが出来ます。

ddtrace-run x sqlite3

以下のような雑な 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 のコンソールを見ると…

f:id:inokara:20170401091349p:plain

発行されたクエリ等の Trace 情報を確認することが出来ます。

Patch

スクリプトの修正

コマンドラインで実行するようなスクリプトであれば 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 にデータが送信されていることを確認出来ます。

f:id:inokara:20170401091414p:plain

Pin で任意のメタデータを付与

また、接続オブジェクトに任意のメタデータ送信データに含めたい場合には以下のように 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 名を変更することが出来ました。

f:id:inokara:20170401091424p:plain

ということで

ざっくりと dd-trace-py を触ってみました。

コマンドラインで実行出来るスクリプトであれば ddtrace-run を利用することで、簡単に Datadog APM にメトリクスを送信することが出来そうです。

引き続き、Flask を利用した雑なアプリケーションで dd-trace-py を深掘り出来ればと考えています。