読者です 読者をやめる 読者になる 読者になる

appengine/pythonのappstatsのページを訳してみるよ

まぁ、おこられるということはない前提で。原文について完全に忠実に訳してはいませんがそんなには外れてないと思います。また、僕の注記は注記とわかるように記述していますが、文中に書いています。

http://code.google.com/appengine/docs/python/tools/appstats.html

WSGIについては、wikipedia程度の知識しかないので、pythonのwebフレームワークまわりの理解の誤りによる誤訳があったらごめんなさい。



appstats
pythonsdkはアプリケーションのパフォーマンス測定ツール一式を含んでおり、それはappstatsと呼ばれています。appstatsはwebアプリケーションフレームワークに記録イベントを統合します。また、取得した統計を確認するためのwebベースの管理インターフェイスも提供しています。

◆ イベントレコーダーをインストールする
◆ 管理インターフェイスをセットアップする
◆ appstatsを設定する
◆ 動作原理をみてみる


◆ イベントレコーダーをインストールする
webのリクエストを記録するために、おのおののリクエストハンドラはappstatsによって呼び出される必要があります。appstatsはハンドラをコードに織り込むための簡単な方法を二つほど用意しています:

・run_wsgi_app関数(google.appengine.ext.webapp.util packageにある)を拡張したもの。これは、あらゆるWSGIフレームワークに対応しており、webappも対応されています。
・Djangoに対応する"middleware"クラス

注記:webappというのはappengine/pyのチュートリアルで紹介されているフレームワークだったと思います。多分。
注記:kay frameworkでも"middleware"クラスを使用するやりかたでappstatsを使用することができます。と、ajnでまつおさんが言ってました。

・run_wsgi_app関数を使用する場合
webappなどのWSGI対応のフレームワークとあわせてappstatsを使用する場合、リクエストハンドラとするスクリプトにおいてrun_wsgi_app関数を使用してアプリケーションを呼び出す必要があります。たとえば、下記のようなコードでwebappのインスタンスを生成し、起動することができます。

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

application = webapp.WSGIApplication([('/', MainPage),
                                      ('/newentry', NewEntry),
                                      ('/editentry', EditEntry),
                                      ('/deleteentry', DeleteEntry),
                                     ],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == '__main__':
    main()

wsgiref.handlers.CGIHandlerなどによってWSGIアプリケーションを呼び出している場合、すべてのハンドラについてrun_wsgi_appを使用するように変更する必要があります。

次に、アプリケーションのルートディレクトリにappengine_config.pyというファイルを作成し、下記の内容を追記します:

def webapp_add_wsgi_middleware(app):
    from google.appengine.ext.appstats import recording
    app = recording.appstats_wsgi_middleware(app)
    return app

run_wsgi_appはこのファイルをインポートし、関数webapp_add_wsgi_middleware(app)があれば呼び出しを行います。

appengine_config.pyについてのより詳細な情報は、「appstatsを設定する」の項を参照してください。


◆ Djangoを使用する場合
Djangoのアプリケーションにapstatsのmiddlewareをインストールするには、下記の記述をsettings.pyのMIDDLEWARE_CLASSESの先頭に追記します。

MIDDLEWARE_CLASSES = (
    'google.appengine.ext.appstats.recording.AppStatsDjangoMiddleware',

    # ...
)

appstatsのmiddlewareは、ほかの要素をプロファイリングするため、先頭にしておくほうが好ましいです。

Djangoのmiddlewareは、必要に応じて、イベントを記録するためにappstatsを呼び出します。その他のアプリケーションコードを書き換える必要はありません。


◆ 管理インターフェイスをセットアップする
appstatsのwebベースの管理インターフェイスを使用できるようにするには、app.yamlに下記のリクエストハンドラを追加します。urlのパスは/stats.*で終了していればどのようなものでも使用することができます。管理インターフェイスを有効にするには、app.yamlに下記のurlハンドラを追記します。

- url: /stats.*
  script: $PYTHON_LIB/google/appengine/ext/appstats/ui.py

この例では、すべての/statsで始まるurlをappstatsの管理インターフェイスに対応させています(urlのパターンが「.*」で終了しているものについては、すべてのurlをその接頭辞のurlに対応させるため)。

注記:僕はpython版のurlマッピングの仕組みを理解してなかったので、サービス呼び出しをする画面を管理インターフェイスのurlで上書いてしまっていてはまりました。python版をあまり使っていない人で試したい人は注意してください。

Tip:appstasのwebインターフェイスはアプリケーションの管理者のみがアクセスすることができます。これは、appstatsのコードで制御されおり、このためハンドラにおいてlogin: adminの制約を設定する必要はありません。ログインについての制約をかけないことによって、appstatsを開発サーバーでより簡単に使用することができるようになっています。

appstatsの管理インターフェイスにアクセスするには、ブラウザからurlを指定してください。上記の通りに手順を進めてきたことを開発サーバーで確認するには、http://localhost:8080/statsにアクセスします。


◆ appstatsを設定する
appstatsの振る舞いは、ルートディレクトリに配置するappengine_config.pyで設定することができます。すべてのオプション設定は、sdkに含まれるgoogle/appengine/ext/appstats/sample_appengine_config.pyを参照してください。

appengine_config.pyについて知っておくべきことを挙げておきます:

・リクエストハンドラがsys.pathを変更しているのであれば、appstatsの管理インターフェイスがすべてのファイルを参照できるようにするために、appengine_config.pyのsys.pathも同様の変更を適用する必要があります。
・Djangoでrun_wsgi_appを使用している場合(上記のmiddlewareの設定を使用していない場合)であり、かつ、google.appengine.dist.use_libraryで Djangoのバージョンを設定している場合、この呼び出しをappengine_config.pyにも追記する必要があります(この設定を行うよりも、middlewareで設定する方法を推奨します)。

注記:sample_appengine_config.pyはこんな感じの内容でした。詳細はファイル自体を見てもらった方がいいですが、簡単に内容を書いておきます。
WSGI middlewareを宣言するサンプル
・Djangoのバージョンを宣言するサンプル
・appstats_DEBUGのサンプル。trueにするとより詳細なトレースが取得できる様子。
・appstats_DUMP_LEVELのサンプル。-1,0,1,2が設定できて、数字が大きい方がいろいろダンプしてくれる。
・appstats_KEY_DISTANCEとappstats_KEY_MODULUSのサンプル。詳細はファイルのコメントに記載がありますが、appstats_KEY_DISTANCEに設定したmsの範囲でおこったリクエストは同じmemcacheのkeyに記録され、appstats_KEY_DISTANCEとappstats_KEY_MODULUSの乗算した結果のmsでkeyがロールオーバーされます。書き味から察するに、より少ないmemcacheの消費でレコードを取得するための設定ですが、ケチると別のリクエストを同じkeyで保存してしまったりするようです。
・appstats_KEY_NAMESPACE、appstats_KEY_PREFIX、appstats_KEY_TEMPLATE、appstats_PART_SUFFIX、appstats_FULL_SUFFIX、appstats_LOCK_SUFFIXの設定。memcacheに入れておく名前空間の設定です。
・appstats_MAX_STACK、appstats_MAX_LOCALS、appstats_MAX_REPR、appstats_MAX_DEPTHの設定。それぞれ、保持する呼び出しスタックの数(stack frameとあるので違うかも)、stack frameに保持するローカル変数の数、保存する変数のstring表現での最大長、listのlistなどの構造化データを保存する深さ、が設定できるようです。
・appstats_RE_STACK_BOTTOM、appstats_RE_STACK_SKIPの設定。呼び出しスタックのボトムとするファイル名の正規表現と、記録から除外するファイルの正規表現です。
・appstats_LOCK_TIMEOUTの設定。memcacheのロックで使用するタイムアウト。
・appstats_TZOFFSETの設定。ログの時間についてタイムゾーンが調整できるようです。
・appstats_stats_urlの設定。まぁ、urlですね。
・appstats_RECORD_FRACTIONの設定。0から1を設定できるようですが、ちょっとよくわかんないです。
・appstats_FILTER_LISTの設定。リクエストの何かがこのリストの要素(要素は環境変数正規表現のdict)にマッチするとログ採取の対象となるようです。なにも設定しなければすべてとります。
・appstats_should_record(env)の関数定義。ここでtrueにしたもののみを記録することができるようです。空気を読む感じだと、ここでappstats_FILTER_LISTを使うみたいです。
・appstats_normalize_path(path)の関数定義。ここでやたら長いurlを短くしたりとかできるみたいです。
・appstats_extract_key(request)の関数定義。appstats_normalize_pathのlow levelなurlの書き換えができるようです。
・どうでもいいですが、roll overとかfractionとか微妙に金融商品っぽいですね。ほんとどうでもいいですが。あと思ったより量が多かったです。。

◆ 動作原理をみてみる
appstatsはappengineのサービスapiのrpc callをフックしています。これは、リクエストハンドラによって行われたすべてのapi呼び出しの統計を採取し、__appstats__名前空間を使用してmemcacheにデータを積みます。appstatsは直近1000までのリクエストの統計を保持します。データは、200byteごとのサマリーレコードと、100kbまでの詳細レコードをとして保持されます。詳細レコードに蓄積しておくデータ量は変更可能です(「appstatsを設定する」の項と、サンプルの設定ファイルを参照してください)。

apiのフックを行うことによって、リクエストハンドラにはオーバーヘッドがかかってしまいます。appstatsはappstatsによって消費されたリソースを"info"レベルのログとして出力します。logは下記のような形式です:

INFO 2009-08-25 12:04:07,277 recording.py:290] Saved; key: __appstats__:046800, part: 160 bytes, full: 25278 bytes, overhead: 0.019 + 0.018; link: http://appid.appspot.com/stats/detail?time=1234567890123

上記のログでは、memcacheのkeyが更新されたこと、サマリーと詳細レコードそれぞれのサイズ、レコードを記録したことによる消費時間(秒)を出力しています。さらに、このイベントの記録を参照することができる管理インターフェイスのリンクも含んでいます。

Tip:appstasはrpcを直接フックするため、管理インターフェイスに表示されるapiの名称はアプリケーションで使用するapiの名称と異なるものです。多くの場合直感的で、例えば、datastore_v3.Getはdb.get()やdb.Model.get()で呼び出されます。datastoreのqueryは、たいていdatastore_v3.RunQueryとdatastore_v3.Nextの呼び出しを行います(RunQueryは最初のいくらかの結果を返すため、apiはNextの呼び出しで多くのデータを呼び出します。不要なNextを減らせば、アプリケーションは高速化されますよ!)。

注記:なにげに僕がつくったのと全然仕組みがちがっていて愕然。python版のほうが遥かに使いやすいですね。もっと精進します。