Hatena::ブログ(Diary)

Memo

2015-03-12

[] MackerelPython クライアント作った

きっかけはこれ。

簡単な Mackerel 用の Dynamic Inventory スクリプトを書いてみました。 Ansible は Python で書かれているので、本当は Python で書くのが筋がよさそうですが、Python クライアントがないので、とりあえず Ruby で書きました。 言語による大した違いはないと思います。

http://yuuki.hatenablog.com/entry/ansible-mackerel-1000

Python クライアントが無かったので作った。

https://github.com/heavenshell/py-mackerel-client


基本的に Rubyクライアントポーティングを行った。

技術的に特にどうこうというのは無いが、初めて使ったのが、cliオプションパーサーに click を使ったくらい。


自分はもう Python3 をメインで使うようになったので、あまり Python2 で確認してない*1

不具合などは GitHub の issues に。

*1:tox で Python2.7 はテストしてるけど

2015-02-22

[] Python でデコレートされているメソッドを取得する方法

Pythonデコレーターでデコレートされている関数/メソッドを全て取得したい。


class Sample(object):
    @decorator
    def foo(self):
        pass

    @decorator
    def bar(self):
        pass

    def baz(self):
        pass

この場合だと、foo(), bar() を取得したい。


inspect.getmembers() を使う。

for func_name, func in inspect.getmembers(instance, inspect.ismethod):
    print(func)

instance には Sample クラスのインスタンスが入っている場合、func に Sample のメソッドが格納される。

このままでは baz() まで取得される。

そのためデコレーターの実装に少し工夫する。

def decorator():
    def _decorator(f):
        f.__sample_event = True 
                    
        @wraps(f)
        def __decorator(func, message, **_kwargs):
            return f(func, message, **_kwargs)
                    
        return __decorator

    return _decorator

instance = Sample()
for func_name, func in inspect.getmembers(instance, inspect.ismethod):
    if getattr(func, '__sample_event', False):
        print(func)

ちょっとトリッキーだけど、これでメソッド一覧を取得できる。

2015-01-30

[] APScheduler

Python で cron みたいなジョブスケジューラなライブラリが無いかと思って探してたら、APScheduler というのを見つけた。

Advanced Python Scheduler — APScheduler 3.1.0.dev1 documentation


Scheduler やジョブを登録する Job stores などを組み合わせて使える。

たとえば Scheduler には Python の Thread を使ってうごく BackgroundScheduler や

Gevent とか、Tornado とかあったりする。


登録したジョブはメモリ上に保存だと、当然スケジューラを再起動したら奇麗に忘れてくれるので、永続化したい場合は Job stores で Redis とか MongoDB をつかって永続化する。


サンプルコードを見ればある程度なにが出来るのか分かりやすい。

agronholm / apscheduler / source / examples / schedulers / background.py — Bitbucket

この場合、BackgroundScheduler に 3 秒ごとに tick() という関数を呼び出すという処理になる。

こんな感じに 3 秒間隔でジョブを呼び出すほかに、cron のように毎日午前 0 時に実行みたいな感じでスケジュールを組んでジョブを実行のようなのが出来る。

apscheduler.triggers.cron — APScheduler 3.1.0.dev1 documentation


ライブラリといよりフレームワークに近いけど、自分がやりたい事が簡単に出来たのでお勧め。

# いくつかハマりポイントがあったが、いずれまた…。

2014-12-31

[] 2014 年振り返り

4 年近く常駐していた所から自社に戻って来た。

まぁ色々あって 11 月後半から年末にかけて障害が続いたりで大変だった。

来年も続く…。

# ことしはこれから年末対応の待機のための出勤


去年の ToDo の振り返り

  • LXC というか Docker
    • 試しにつかってみた程度。実プロダクションでは使えてないし、ユースケース的にまだあってない
  • Asinble
    • これは使った
    • 今後も使っていく
  • Marionette.js
    • 使えてない
    • 他のも検討した方がいいんじゃないかと思ってるがまだ様子見
  • JavaScript のテストコード(主に E2E テスト)
    • Mocha とか一通り使い方は分かった
    • 引き続きやっていく
  • Python3 に本格移行
    • ほぼ移行できた
    • ただ昨日書いていたコードが Python2 では落ちないけど、Python3 では落ちるという現象があった
    • もう少し追いかけてみないと
    • 少なくともあと 1 年くらいは Python2 と Python3 両方で動く書き方すると思う
  • Golang

来年の個人的な目標は今年と同じ。

2014-12-18

[] 雑談対話用の Python クライアント作った

リフレッシュ休暇中で、出かけようと思ったけど天気悪いし、寒いし、雪降るし…なので docomo が提供している APIPython クライアントを作った。


元ネタ。

docomoが提供している雑談対話APIを利用し、docomoruというライブラリを使いながらBOTと雑談する方法について説明します。

ChatOps - DocomoruでBOTと雑に会話する - Qiita

DoCoMo が雑談対話APIというのを出していたのでサクッっとlingr botを書いてみた。

Big Sky :: golang で vimgirl bot を書いた。

現状雑談対話 APIライブラリだけ。

気が向けば他のも作るかも。

heavenshell/py-doco ? GitHub


使い方

>>> from doco.client import Client
>>> c = Client(apikey='YOUR_API_KEY')
>>> res = c.send(utt='hello', apiname='Dialogue')
>>> print(res)
{"utt":"はろー","yomi":"はろー","mode":"dialog","da":"30","context":"7DGIKMpQDE0zrQrYFAMqdw"}
>>> print(c.last_response.status_code)
200
>>> print(c.last_response.headers)
{'Content-Length': '99', 'Connection': 'keep-alive', 'Content-Type': 'application/json;charset=UTF-8', 'Date': 'Wed, 17 Dec 2014 05:28:28 GMT', 'asyncServiceInvoke': 'false'}

こんな感じで使う。

コンストラクタ引数雑談対話 | docomo Developer support | NTTドコモ に記載されているリクエストパラメータを付与する事が出来る。

>>> from doco.client import Client
>>> user = { 'nickname': 'ふー', 'nickname_y': 'フー', 'bloodtype': 'O', 'birthdateY': 1997, 'birthdateM': 12, 'birthdateD': 31 }
>>> c = Client(apikey='YOUR_API_KEY', user=user)

あるいは send に渡す事ができる。

>>> from doco.client import Client
>>> user = { 'nickname': 'ふー', 'nickname_y': 'フー', 'bloodtype': 'O', 'birthdateY': 1997, 'birthdateM': 12, 'birthdateD': 31 }
>>> c = Client(apikey='YOUR_API_KEY')
>>> c.send(utt='こんにちは', apiname='Dialogue', **user)

cli アプリケーション

ついでにサンプルとして、cli アプリケーションとして、インタラクティブシェルなものを作ってみた。

$ python examples/dialogue_cli.py -i
Welcome to docomo dialogue cli.
^D to exit.
>>> こんにちは
はろー
>>>

適当に会話してくれる。


キャラ

キャラ設定もできる。

$ python examples/dialogue_cli.py -i -t 30
Welcome to docomo dialogue cli.
^D to exit.
>>> げんき?
元気でちゅ
>>>

20 が関西弁で、30 が赤ちゃん。


しりとり

雑談対話 API には前の会話を引き継ぐ。

その機能として「しりとり」機能がある。

$ python examples/dialogue_cli.py -i -s
Welcome to docomo dialogue cli.
^D to exit.
>>> しりとり
リング
>>> グッズ
頭痛
>>> 海
道のり
>>> リンゴ
ゴリラ
>>> ラッパ
パスポート
>>>

ちゃんと「しりとり」してくれる。


実装

実装としては API ごとにクラスを作って、Client() に register_api というのがあるので、そいつ経由で登録して、send() を呼ぶ感じ。

デフォルトでは雑談対話の Dialogue を登録してある。


遊ぶネタが出来たので、Slack と連携させて bot を飼うかなー。

2014-11-29

[] log

割と大きい障害が起きた。

うちはデータストアとそれを使う検索システムを作って 1 年くらい前に納品した。

7 月に自分がサポートを引き継いだ。

ちょくちょくメールで問い合わせがあったりして、まぁ分かる範囲で対応してた。


エンドユーザの環境に入れないので、ベンダーからログを貰って解析してーという流れ。

このログが消えてたり、中途半端に出てなかったり、かと思えば、検索システムのほうはログが出過ぎていたり…で割とこまる。

特に無いのは非常に困る。

今回もデータストアを再起動した際にデータが一部消えていて、その原因が結局ログが一部消失した事もあって決定的な原因が分からなかった。



開発中に必要なログと運用時に必要なログは別物だと思う。

ログなさすぎ問題、ログ出し過ぎ問題…。難しい。

まぁ無いより、あった方が良いに決まってる。

無いと本当になにも出来ない。

どの粒度でどんなログを出せば良いのか、何か体系だってまとまってたりしないかな。

2014-10-31

[] locust.io でカスタムクライアントを作る

お仕事で自社製の分散 KVS の環境構築をやっている。

運用試験や負荷試験を行うんだけど、負荷試験を行うのにツールを使いたい。

Python 製の locust.io を使う。


locust.io を選んだ理由は

というのが主な理由。


というわけで locust.io のカスタムクライアントの作り方を備忘録として残す。

オフィシャルサイトXML-RPCClient について書かれているので基本的にこれを真似する。

Testing other systems using custom clients — Locust 0.7.2 documentation


今回はサンプルとして Redis を使ってみる。

# -*- coding: utf-8 -*-
import time
import redis
from locust import Locust, events, task, TaskSet

class RedisClient(object):
    def __init__(self, hosts):
        r = redis.StrictRedis(host='localhost', port=6379, db=0)
        self.client = r

    def set(self, key, value):
        start_time = time.time()
        try:
            ret = self.client.set(key, value)
            total_time = int((time.time() - start_time) * 1000)
            if ret is True:
                events.request_success.fire(request_type='redis',
                                            name='set',
                                            response_time=total_time,
                                            response_length=0)
            else:
                events.request_failure.fire(request_type='redis',
                                            name='set',
                                            response_time=total_time,
                                            exception=None)
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            events.request_failure.fire(request_type='redis',
                                        name='set',
                                        response_time=total_time,
                                        exception=e)

        return ret

    def get(self, key):
        start_time = time.time()
        try:
            ret = self.client.get(key)
            total_time = int((time.time() - start_time) * 1000)
            if ret:
                events.request_success.fire(request_type='redis',
                                            name='get',
                                            response_time=total_time,
                                            response_length=0)
            else:
                events.request_failure.fire(request_type='redis',
                                            name='get',
                                            response_time=total_time,
                                            exception=None)
        except Exception as e:
            events.request_failure.fire(request_type='redis',
                                        name='get',
                                        response_time=total_time,
                                        exception=e)
        return ret


class RedisLocust(Locust):
    def __init__(self, *args, **kwargs):
        super(RedisLocust, self).__init__(*args, **kwargs)
        self.client = RedisClient()


class RedisUser(RedisLocust):
    min_wait = 10
    max_wait = 1000

    class task_set(TaskSet):        
        @task(1)
        def set_key(self):
            self.client.set('foo', 'bar')

        @task(1)
        def get_key(self, **kwargs):
            self.client.get('foo')

locust のレポートで一件あたりのリクエストに掛かった時間を出力する必要があるので、Redis の get や set を独自のクライアントでラップしてやる。

locust.io のドキュメントの XML-RPCメソッド自体をラップしているが、直感的ではなかったので、こうした。

# 多少のオーバーヘッドは出そうな気はする


基本的に get や set を実行前にタイマーを開始し、メソッドを実行後にタイマーを止めて時間を計測して、それを locust.io のイベントを発火してやれば終わり。


カスタムクライアントを書く事が出来るのは本当に素晴らしい。

因みに自社の分散 KVS に locust.io から繋げるために Python 用のクライアントを作ったけど、これはまた別の機会に。