gepuro.net
gepulog

データ分析エンジニアによる備忘録的ブログ

webビーコンを作った

google analyticsでアクセスログを取ってるのだけれど、セッション毎のデータを利用するのは有料だったので、自分で作ってみた。

な感じのプログラムに対して、

<script type="text/javascript">document.write("<img src='http://beacon.gepuro.net/?site=blog.gepuro.net");document.write("&referrer="+encodeURIComponent(document.referrer)+"'>");</script>
な感じにタグを埋め込んで使う。

このサイトでデータを集めて、何かに使うつもり。

実行速度のボトルネックをplopで見つける

2016/10/6の追記

python3 setup.py install

でインストールするだけで良く、下にあるソースコードの変更は不必要になった。

以降は2016/9/5に書いた記事

Plop: Low-overhead profiling for Pythonにあるようにdropboxの中の人が開発したplopというツールがある。

これを使えば、プログラムの累積実行時間をメソッド単位で計測でき、コールグラフも作成出来る。pythonが遅いと感じた時に、簡単にボトルネックになっている箇所を発見出来るので便利!

しかしながら、https://github.com/bdarnell/plopにあるコードはpython2系でしか動かないので、3系でも動くように修正した。

面倒だったので、計測用のコードしか修正してないです。可視化は2系のまま。

使い方

計測

python3 -m plop.collector myscript.py

可視化

python -m plop.viewer --datadir=/tmp

コード

collector.py

from __future__ import with_statement
import collections
import signal
import sys
import _thread as thread
from plop import platform
import time

class Collector(object):
    MODES = {
        'prof': (platform.ITIMER_PROF, signal.SIGPROF),
        'virtual': (platform.ITIMER_VIRTUAL, signal.SIGVTALRM),
        'real': (platform.ITIMER_REAL, signal.SIGALRM),
        }

    def __init__(self, interval=0.01, mode='virtual'):
        self.interval = interval
        self.mode = mode
        assert mode in Collector.MODES
        timer, sig = Collector.MODES[self.mode]
        signal.signal(sig, self.handler)
        self.reset()

    def reset(self):
        # defaultdict instead of counter for pre-2.7 compatibility
        self.stack_counts = collections.defaultdict(int)
        self.samples_remaining = 0
        self.stopping = False
        self.stopped = False

        self.samples_taken = 0
        self.sample_time = 0

    def start(self, duration=30.0):
        self.stopping = False
        self.stopped = False
        self.samples_remaining = int(duration / self.interval)
        timer, sig = Collector.MODES[self.mode]
        platform.setitimer(timer, self.interval, self.interval)

    def stop(self):
        self.stopping = True
        self.wait()

    def wait(self):
        while not self.stopped:
            pass # need busy wait; ITIMER_PROF doesn't proceed while sleeping

    def handler(self, sig, current_frame):
        start = time.time()
        self.samples_remaining -= 1
        if self.samples_remaining <= 0 or self.stopping:
            platform.setitimer(Collector.MODES[self.mode][0], 0, 0)
            self.stopped = True
            return
        current_tid = thread.get_ident()
        for tid, frame in sys._current_frames().items():
            if tid == current_tid:
                frame = current_frame
            frames = []
            while frame is not None:
                code = frame.f_code
                frames.append((code.co_filename, code.co_firstlineno, code.co_name))
                frame = frame.f_back
            self.stack_counts[tuple(frames)] += 1
        end = time.time()
        self.samples_taken += 1
        self.sample_time += (end - start)

    def filter(self, max_stacks):
        self.stack_counts = dict(sorted(self.stack_counts.items(), key=lambda kv: -kv[1])[:max_stacks])


def main():
    # TODO: more options, refactor this into somewhere shared
    # between tornado.autoreload and auto2to3
    if len(sys.argv) >= 3 and sys.argv[1] == '-m':
        mode = 'module'
        module = sys.argv[2]
        del sys.argv[1:3]
    elif len(sys.argv) >= 2:
        mode = "script"
        script = sys.argv[1]
        sys.argv = sys.argv[1:]
    else:
        print("usage: python -m plop.collector -m module_to_run")
        sys.exit(1)


    collector = Collector()
    collector.start(duration=3600)
    exit_code = 0
    try:
        if mode == "module":
            import runpy
            runpy.run_module(module, run_name="__main__", alter_sys=True)
        elif mode == "script":
            with open(script) as f:
                global __file__
                __file__ = script
                # Use globals as our "locals" dictionary so that
                # something that tries to import __main__ (e.g. the unittest
                # module) will see the right things.
                exec(f.read(), globals())
    except (SystemExit) as e:
        exit_code = e.code
    collector.stop()
    collector.filter(50)
    with open('/tmp/plop.out', 'w') as f:
        f.write(repr(dict(collector.stack_counts)))
    print("profile output saved to /tmp/plop.out")
    overhead = float(collector.sample_time) / collector.samples_taken
    print("overhead was %s per sample (%s%%)" % (
        overhead, overhead / collector.interval))
    sys.exit(exit_code)


if __name__ == '__main__':
    main()

変更点だけなら

--- plop/collector.py   2015-09-01 16:34:42.000000000 +0900
+++ /usr/local/lib/python3.4/dist-packages/plop/collector.py    2015-09-01 16:36:38.515175381 +0900
@@ -1,20 +1,17 @@
 from __future__ import with_statement
 import collections
-import os
 import signal
 import sys
-import thread
+import _thread as thread
+from plop import platform
 import time
-import argparse
-import plop.platform
-

 class Collector(object):
     MODES = {
-        'prof': (plop.platform.ITIMER_PROF, signal.SIGPROF),
-        'virtual': (plop.platform.ITIMER_VIRTUAL, signal.SIGVTALRM),
-        'real': (plop.platform.ITIMER_REAL, signal.SIGALRM),
-    }
+        'prof': (platform.ITIMER_PROF, signal.SIGPROF),
+        'virtual': (platform.ITIMER_VIRTUAL, signal.SIGVTALRM),
+        'real': (platform.ITIMER_REAL, signal.SIGALRM),
+        }

     def __init__(self, interval=0.01, mode='virtual'):
         self.interval = interval
@@ -22,11 +19,11 @@
         assert mode in Collector.MODES
         timer, sig = Collector.MODES[self.mode]
         signal.signal(sig, self.handler)
-        signal.siginterrupt(sig, False)
         self.reset()

     def reset(self):
-        self.stacks = list()
+        # defaultdict instead of counter for pre-2.7 compatibility
+        self.stack_counts = collections.defaultdict(int)
         self.samples_remaining = 0
         self.stopping = False
         self.stopped = False
@@ -39,7 +36,7 @@
         self.stopped = False
         self.samples_remaining = int(duration / self.interval)
         timer, sig = Collector.MODES[self.mode]
-        plop.platform.setitimer(timer, self.interval, self.interval)
+        platform.setitimer(timer, self.interval, self.interval)

     def stop(self):
         self.stopping = True
@@ -47,13 +44,13 @@

     def wait(self):
         while not self.stopped:
-            pass  # need busy wait; ITIMER_PROF doesn't proceed while sleeping
+            pass # need busy wait; ITIMER_PROF doesn't proceed while sleeping

     def handler(self, sig, current_frame):
         start = time.time()
         self.samples_remaining -= 1
         if self.samples_remaining <= 0 or self.stopping:
-            plop.platform.setitimer(Collector.MODES[self.mode][0], 0, 0)
+            platform.setitimer(Collector.MODES[self.mode][0], 0, 0)
             self.stopped = True
             return
         current_tid = thread.get_ident()
@@ -65,135 +62,56 @@
                 code = frame.f_code
                 frames.append((code.co_filename, code.co_firstlineno, code.co_name))
                 frame = frame.f_back
-            self.stacks.append(frames)
+            self.stack_counts[tuple(frames)] += 1
         end = time.time()
         self.samples_taken += 1
         self.sample_time += (end - start)

-
-class CollectorFormatter(object):
-    """
-    Abstract class for output formats
-    """
-    def format(self, collector):
-        raise Exception("not implemented")
-
-    def store(self, collector, filename):
-        with open(filename, "wb") as f:
-            f.write(self.format(collector))
-
-
-class PlopFormatter(CollectorFormatter):
-    """
-    Formats stack frames for plop.viewer
-    """
-    def __init__(self, max_stacks=50):
-        self.max_stacks = 50
-
-    def format(self, collector):
-        # defaultdict instead of counter for pre-2.7 compatibility
-        stack_counts = collections.defaultdict(int)
-        for frames in collector.stacks:
-            stack_counts[tuple(frames)] += 1
-        stack_counts = dict(sorted(stack_counts.iteritems(),
-                                   key=lambda kv: -kv[1])[:self.max_stacks])
-        return repr(stack_counts)
-
-
-class FlamegraphFormatter(CollectorFormatter):
-    """
-    Creates Flamegraph files
-    """
-    def format(self, collector):
-        output = ""
-        previous = None
-        previous_count = 1
-        for stack in collector.stacks:
-            current = self.format_flame(stack)
-            if current == previous:
-                previous_count += 1
-            else:
-                output += "%s %d\n" % (previous, previous_count)
-                previous_count = 1
-                previous = current
-        output += "%s %d\n" % (previous, previous_count)
-        return output
-
-    def format_flame(self, stack):
-        funcs = map("{0[2]} ({0[0]}:{0[1]})".format, reversed(stack))
-        return ";".join(funcs)
+    def filter(self, max_stacks):
+        self.stack_counts = dict(sorted(self.stack_counts.items(), key=lambda kv: -kv[1])[:max_stacks])


 def main():
     # TODO: more options, refactor this into somewhere shared
     # between tornado.autoreload and auto2to3
-    parser = argparse.ArgumentParser(description="Plop: Python Low-Overhead Profiler",
-                                     prog="python -m plop.collector",
-                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-    parser.add_argument("--format", "-f", help="Output format",
-                        choices=["plop", "flamegraph"], default="plop")
-    parser.add_argument("--module", "-m", help="Execute target as a module",
-                        action="store_const", const=True, default=False)
-    parser.add_argument("--mode", help="Interval timer mode to use, see `man 2 setitimer`",
-                        choices=["prof", "real", "virtual"], default="prof")
-    parser.add_argument("--interval", help="Timer interval in seconds", default=0.01, type=float)
-    parser.add_argument("--duration", help="Profiling duration in seconds", default=3600,
-                        type=int)
-    parser.add_argument("--max-stacks", help=("Number of most frequent stacks to store."
-                        " Ignored for Flamegraph output."), type=int, default=50)
-    parser.add_argument("target", help="Module or script to run")
-    parser.add_argument("arguments", nargs=argparse.REMAINDER,
-                        help="Pass-through arguments for the profiled application")
-    args = parser.parse_args()
-    sys.argv = [args.target] + args.arguments
-
-    if args.format == "flamegraph":
-        extension = "flame"
-        formatter = FlamegraphFormatter()
-    elif args.format == "plop":
-        extension = "plop"
-        formatter = PlopFormatter(max_stacks=args.max_stacks)
+    if len(sys.argv) >= 3 and sys.argv[1] == '-m':
+        mode = 'module'
+        module = sys.argv[2]
+        del sys.argv[1:3]
+    elif len(sys.argv) >= 2:
+        mode = "script"
+        script = sys.argv[1]
+        sys.argv = sys.argv[1:]
     else:
-        sys.stderr.write("Unhandled output format: %s" % args.format)
-        sys.stderr.flush()
+        print("usage: python -m plop.collector -m module_to_run")
         sys.exit(1)
+    

-    if not os.path.exists('profiles'):
-        os.mkdir('profiles')
-    filename = 'profiles/%s-%s.%s' % (args.target, time.strftime('%Y%m%d-%H%M-%S'),
-                                      extension)
-
-    collector = Collector(mode=args.mode, interval=args.interval)
-    collector.start(duration=args.duration)
+    collector = Collector()
+    collector.start(duration=3600)
     exit_code = 0
     try:
-        if args.module:
+        if mode == "module":
             import runpy
-            runpy.run_module(args.target, run_name="__main__", alter_sys=True)
-        else:
-            with open(args.target) as f:
-                # Execute the script in our namespace instead of creating
-                # a new one so that something that tries to import __main__
-                # (e.g. the unittest module) will see names defined in the
-                # script instead of just those defined in this module.
+            runpy.run_module(module, run_name="__main__", alter_sys=True)
+        elif mode == "script":
+            with open(script) as f:
                 global __file__
-                __file__ = args.target
-                # If __package__ is defined, imports may be incorrectly
-                # interpreted as relative to this module.
-                global __package__
-                del __package__
-                exec f.read() in globals(), globals()
-    except SystemExit, e:
+                __file__ = script
+                # Use globals as our "locals" dictionary so that
+                # something that tries to import __main__ (e.g. the unittest
+                # module) will see the right things.
+                exec(f.read(), globals())
+    except (SystemExit) as e:
         exit_code = e.code
     collector.stop()
-    if collector.samples_taken:
-        formatter.store(collector, filename)
-        print "profile output saved to %s" % filename
-        overhead = float(collector.sample_time) / collector.samples_taken
-        print "overhead was %s per sample (%s%%)" % (
-            overhead, overhead / collector.interval)
-    else:
-        print "no samples collected; program was too fast"
+    collector.filter(50)
+    with open('/tmp/plop.out', 'w') as f:
+        f.write(repr(dict(collector.stack_counts)))
+    print("profile output saved to /tmp/plop.out")
+    overhead = float(collector.sample_time) / collector.samples_taken
+    print("overhead was %s per sample (%s%%)" % (
+        overhead, overhead / collector.interval))
     sys.exit(exit_code)

Atom フィード形式に対応しました

Generating Feeds with Flaskを参考にして、当ブログをAtomフィード形式に対応させました。こちらです。

コードのハイライティングを出来るようにした

Pygmentsを

pip install Pygments

でインストールして、

markdown.markdown(html, ['fenced_code', 'codehilite'])

とすれば良い。 あとは、 https://github.com/richleland/pygments-css からお気に入りのハイライティングを選んできて、適切な箇所に置く。

以下がサンプル

print "Hello World"
print "Hello World"
#!/usr/bin/python
# -*- coding:utf-8 -*-
print "Hello World"

参考:

追記

markdownは2.1.1、Pygmentsは1.6で動作確認をしている。バージョンが異なると挙動が変わる模様。

似てない記事の推薦エンジンのバグを直した

先日より、各記事の下部に似てない記事を表示させていたが、記事によって表示されないものがあった。原因が分かったので、修正を行いました。

cos類似度を計算する時に、各記事の組み合わせを作成する箇所でバグがありました。 本来は、

itertools.permutations
とするところを、
itertools.combinations
としていました。

現段階では、A,Bの類似度とB,Aの類似度を二度計算しているので、記事の数が増えてくると修正を加える必要が出てきそうです。

参考:

ネストされたリストを平坦にする

Python で flatten - ネストしたリストをフラットにするを参考にして、タプルが含まれていても、実行出来るようにした。

def flatten(L):
    if isinstance(L, (list, tuple)):
        if L == [] or L == ():
            return []
        else:
            return flatten(L[0]) + flatten(L[1:])
    else:
        return [L]
isinstanceの箇所を変更しただけです。

PythonからPostgreSQLにアクセスする

psycopg2を使うと便利なようです。 以下は、最低限必要になりそうなサンプル。

import psycopg2
con = psycopg2.connect(
    database = "database"
    , user = "username"
    , password = "password"
    , host = "host"
    , port = 5432
    )
cur = con.cursor()
cur.execute("select * from tablename")
cur.fetchall()

第31回R勉強会@東京(#TokyoR)で発表してきました

タイトルは、「RとPythonによるデータ解析入門」です。

第11回 集合知プログラミング勉強会で発表しました

集合知プログラミング勉強会の最終回でした。 テーマは、遺伝的プログラミングです。

近いうちに、はじめてのパターン認識の勉強会が開かれるそう。

参加したいな。

Flaskでhttpsを使う

認証を使うようなサービスは、SSLを用いてセキュアな通信をしたい。 Flask自身が持ってるwebサーバでは、SSLが使えないので、 apacheやwsgiの力を借りる必要がある。

まず、オレオレ証明書を作る。

$ openssl genrsa -aes128 1024 > server.key
$ openssl req -new -key server.key > server.csr
$ openssl x509 -in server.csr -days 365 -req -signkey server.key > server.crt

とこんな感じで作れる。 作成したものは、それぞれ

  • server.key :秘密鍵
  • server.csr :公開鍵
  • server.crt :デジタル証明書

である。

apacheの設定は、SSLの方は、

LoadModule ssl_module modules/mod_ssl.so
#Listen 443
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl    .crl
SSLPassPhraseDialog builtin
SSLSessionCache     shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout 300
SSLMutex default
SSLRandomSeed startup file:/dev/urandom 256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin

<VirtualHost *:443> ServerName example.com WSGIScriptAlias / /home/hogehoge/apps/hello.wsgi SSLEngine On SSLProtocol all -SSLv2 SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HiGH:+MEDIUM:+LOW SSLCertificateFile /home/hogehoge/apps/server.crt SSLCertificateKeyFile /home/hogehoge/apps/server.key

<Directory /home/hogehoge/apps> WSGIProcessGroup hello WSGIApplicationGRoup %{GLOBAL} WSGIScriptReloading On Order deny,allow Allow from all </Directory>

</VirtualHost>

となる。 通常のhttpの方は、
<VirtualHost *:80>
ServerName example.com
WSGIDaemonProcess hello user=www-data group=www-data
WSGIScriptAlias / /home/hogehoge/apps/hello.wsgi
<Directory /home/hogehoge/apps>
WSGIProcessGroup hello
WSGIApplicationGRoup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
とした。

サービス本体は、 hello.py

#!/usr/bin/python
# -+- coding:utf-8 -*-

from flask import Flask app = Flask(__name__)

@app.route("/") def hello(): return "Hello World!"

if __name__ == "__main__": app.run()

また、hello.wsgiは、
#!/usr/bin/python
# -+- coding:utf-8 -*-
import sys
sys.path.insert(0, '/home/hogehoge/apps')

from hello import app as application

としておく。

これで、https://127.0.0.1/ でアクセス出来るようになった。

参考:

scikit-learnのインストールをした

Debian系のOSを使っている人は、

$ sudo apt-get install python-sklearn
でインストール出来る。

最近、scikit-learnが流行って来ているようなので、少しずつ使っていきたいな。

まずは、インストールの確認ということで、

$ ipython
Python 2.7.3 (default, Sep 26 2012, 21:51:14) 
Type "copyright", "credits" or "license" for more information.

IPython 0.13.1.rc2 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details.

In [1]: import sklearn

In [2]: sklearn.__version__ Out[2]: '0.11'

インストールは無事に出来ているようです。

最新版は0.13.1なので、一部動かないものがあるかもしれない。

Djangoを始める

画像ベースのサイトを作ってみたいかもしれない に向けて、DjangoPostgreSQLでサイトを構築して行きたいと思う。

まずは、Djangoの利用の準備を始める。 データベースには、PostgreSQLの利用を想定する。

LinuxMintでのインストール方法

$ sudo apt-get install python-django

PostgreSQLのインストール

$ sudo apt-get install postgresql
$ sudo apt-get install postgresql-client

PostgreSQLのPython用のモジュール

$ sudo apt-get install python-psycopg2


プロジェクトを開始する

$ django-admin startproject gepupic
でプロジェクトを開始できる。

すると、gepupicというフォルダが作成され、

gepupic/
    manage.py
    gepupic/
          __init__.py
          settings.py
          urls.py
          qsgi.py
という構成でファイルが作成される。

開発用サーバーを起動する時は、

$ python manage.py runserver
Validating models...

0 errors found Django version 1.4.1, using settings 'gepupic.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

というように起動する。 ここで、http://127.0.0.1:8000にアクセスすると、 デモ画面が表示される。

次に、settings.pyの編集を行う DATABASESの所を

'ENGINE': 'django.db.backends.postgresql_psycopg2'
として、あとは、NAME,USER,PASSWORD,HOST,PORTと設定を行う。 また、LANGUAGE_CODE='ja'とした。 ここで、PostgreSQLにてユーザとデータベースを作成しておく。
$ createuser -a -d -U postgres -P ユーザー名
でユーザを追加出来る。 また、
$ psql
> CREATE DATABASE gepupic;
などと、データベースを作成する。これらの操作を行う時に、su postgresしておく必要があるかもしれない。

データベースの作成が終わったら、

$ python manage.py syncdb
と実行すると、データベースの設定が行われる。

モデルの作成は、

$ python manage.py startapp apps
とする。すると、
apps/
    __init__.py
    models.py
    tests.py
    views.py
が作成される。

今日は、このあたりで。 徐々に開発していきます。

参考:はじめての Django アプリ作成、その 1

設定ファイルを書く時はConfigParser

何かプログラムを書いているときに、設定ファイルをどうするか悩むときがある。プログラムの初めの方に定数として宣言しておいて、そこを編集させるか、別のファイルに書かせるかの二通りが考えられそう。

設定項目が短い場合は、JSONで記述するのも便利そうだが、長くなってくると見難くなりそうと感じる。

今回紹介するライブラリは、ConfigParserです。Pythonで利用出来ます。

import ConfigParser
CONFIG_FILE = 'settings.ini'
conf = ConfigParser.SafeConfigParser()
conf.read(CONFIG_FILE)
USERNAME = conf.get('credential', 'username')
PASSWORD = conf.get('credential', 'password')
host = conf.get('options','host')
port     = conf.get('options','port')

Pythonコードは、上のように書くだけで良いです。

設定ファイル``settings.ini''は、

[credential]
username=ユーザー名
password=パスワード

[options] host=127.0.0.1 port=5000

とこんな感じです。

割と簡単に書けるので、今後も使っていきたいですね。

cPickleでPythonオブジェクトを保存する

Pythonでは、cPickle(またはpickle)、shelveというように変数を永続化することが出来るモジュールがあります。

用途としては、データベースを使用するほどの規模ではないが、データを保存しておきたい時に便利です。

shelveを使えば、辞書型を保存しておくことができます。 今回、紹介するcPickleは、Pythonオブジェクトをシリアライズして保存することができます。例えば、辞書型、配列型、文字列といった様々なデータを保存しておくことが出来ます。

cPickleとPickleの大きな違いは、実行速度です。cPickleの方が最大で1000倍早いそう。(自身では未確認)

このgepulogでは、cPickleを利用しています。使っている場所は、サイドバーにあるカテゴリと最近の更新の部分です。こちらのデータは、変化が少なく、同じデータを何度も表示するので、データベースへ毎回アクセスするよりも早くなるはず。(サイトのレスポンスを表示してくれるサイトでは早くなっているように見える)

編集者用の画面をもう少し直したいなあ。

Python-Markdownが便利!

gepulogで、記事を書く際に利用しているライブラリの一つであるPython-Markdownの紹介です。

Markdownというのは、文書を書くときに記述するマークアップ言語で、プレーンテキストをhtmlに変換してくれる優れものです。Pythonでの利用方法は、簡単で、

#!/usr/bin/python
# -- coding:utf-8 --
import markdown
text = u"プレーンテキスト"
print markdown.markdown(text)
で、利用できます。すると出力は、
<p>プレーンテキスト</p>
のように出力されます。

書き方は、http://daringfireball.net/http://ja.wikipedia.org/wiki/Markdownを参考にすると良さそうです。自分自身、まだ使い始めて間も無いので、調べながらでないと書けないけれども、慣れると便利になりそうですね。