7.28.2013

Reading Event Data on Google Calendar in Python

Python: Google Calendar のイベント情報を読み取るスクリプト

 

ライブラリのインストール

google-api-python-client を利用する。

google-api-python-client - Google APIs Client Library for Python - Google Project Hosting

$ sudo pip install google-api-python-client

 

認証用のファイルを準備

このような画面操作でサンプルを手に入れることができる。それを参考にする。

Installation  Google APIs Client Library for Python  Google Developers

認証は、client_secrets.json というJSON形式のファイルを使うのが推奨されているようだ。
このファイルも手打ちする必要はなく、ダウンロードボタン一発で入手できる。 

Client Secrets - Google APIs Client Library for Python — Google Developers

サンプルをたたくと、初回に client_secrets.json を元にFlow オブジェクトが生成される。

このとき、どのAPIにどのレベルでアクセスするかを指定する。
今回はカレンダーの読み取りだけなので、'https://www.googleapis.com/auth/calendar.readonly' があれば十分。

その内容が Storage オブジェクトとなり、ファイルに保存される。
今回は read_calendar.dat というファイル名にした。 

認証情報(credentials)が完成したら、httplib2、build() 関数を使って Calendar API v3 にアクセスすればよい。 

 

コード

今日一日の予定を出力するコード。
プログラムの第一引数に整数を入れれば、n日後の予定を取得できる。

対象のカレンダーIDは予め調べて、ハードコーディング。

予定の開始時刻、終了時刻、内容、作成者をソートして出力するため、OneDayEvent というクラスを作っている。

タイムゾーンの調整はしていない。(手抜き)
もっとコメント書いた方がいいけどこれも手抜き。

Calendar API のサービスを取得した後、
service.events()list(...).execute() で条件に見合ったイベントのリストを取得している。

read_calendar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import httplib2
import os
import sys
import datetime
 
from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import flow_from_clientsecrets
from oauth2client.tools import run
 
# Calendar id for reading.
CALENDAR_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxxx@xxxxx.calendar.google.com'
 
# Path to the credential files.
BASE_DIR = lambda x: os.path.join(os.path.dirname(__file__), x)
 
CLIENT_SECRETS_PATH = BASE_DIR('client_secrets.json')
STORAGE_PATH = BASE_DIR('read_calendar.dat')
 
# Set up a Flow object to be used for authentication.
FLOW = flow_from_clientsecrets(CLIENT_SECRETS_PATH, scope=[
])
 
 
def get_calendar_service():
    storage = Storage(STORAGE_PATH)
    credentials = storage.get()
 
    if credentials is None or credentials.invalid:
        credentials = run(FLOW, storage)
 
    http = httplib2.Http()
    http = credentials.authorize(http)
 
    service = build('calendar', 'v3', http=http)
    return service
 
 
class OneDayEvent(object):
    def __init__(self, start_time, end_time, summary, creator):
        if start_time is None:
            assert(end_time is None)
        self.start_time = start_time
        self.end_time = end_time
        self.summary = summary
        self.creator = creator
 
    def __lt__(self, that):
        if self.start_time is None and that.start_time is None:
            return self.summary < that.summary
        if self.start_time is None or that.start_time is None:
            return self.start_time is None
        return (self.start_time, self.end_time, self.summary, self.creator) < (
            that.start_time, that.end_time, that.summary, that.creator)
 
    def __str__(self):
        if self.start_time is None:
            tm = u'終日'
        else:
            f = lambda x: datetime.datetime.strftime(x, '%H:%M')
            tm = '%s-%s' % (f(self.start_time), f(self.end_time))
        return u'[%s] %s (%s)' % (tm, self.summary, self.creator)
 
 
def read_events(date):
    """Returns sorted list of OneDayEvent objects."""
 
    def format(x):
        return datetime.datetime.strftime(x, '%Y-%m-%dT%H:%M:%SZ')
 
    time_min = datetime.datetime.combine(date, datetime.time())
    time_max = time_min + datetime.date.resolution
 
    time_min, time_max = map(format, (time_min, time_max))
 
    service = get_calendar_service()
    events = service.events().list(
        calendarId=CALENDAR_ID, timeMin=time_min, timeMax=time_max).execute()
 
    def f(x):
        y = x.get('dateTime')
        if y:
            z = datetime.datetime.strptime(y[:-6], '%Y-%m-%dT%H:%M:%S')
            return datetime.datetime.combine(date, z.time())
 
    ret = [OneDayEvent(
        f(event['start']), f(event['end']), event['summary'],
        event['creator']['displayName']) for event in events['items']]
    ret.sort()
    return ret
 
 
def main(argv):
    offset = int(argv[1]) if len(argv) >= 2 else 0
 
    for event in read_events(
            datetime.date.today() + datetime.date.resolution * offset):
        print(unicode(event))
 
if __name__ == '__main__':
    main(sys.argv)

 

 

  • 出力例
    [終日] 有給休暇 (James LaBrie)
    [11:00-12:00] ○○様来社 (John Petrucci)
    [14:00-15:00] [外出] △△訪問 (John Myung)
    [17:30-22:00] □□勉強会 (Jordan Rudess)
    [17:30-23:00] ☆☆飲み会 (Mike Mangini)

 

References

 

Python: Sending IRC Message, Improved

Python: IRCメッセージ送信処理の改善

 

こちらのエントリの改善。
mog project: How to Send an IRC Message with SSL in Python

複数のメッセージを一度に送れるようにした。

あと、disconnect() の時にサーバでエラーが出ていたので削除した。

irc_message.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*- coding: utf-8 -*-
  
import ssl
  
SERVER_ADDRESS = 'xxx.xxx.xxx.xxx'
SERVER_PORT = 6667
SERVER_PASS = 'xxxxxx'
  
LOGIN_NAME = 'xxxxxx'
LOGIN_CHANNEL = '#xxxxxx'
 
 
def send(message):
    try:
        import irc.client
        import irc.connection
 
        if isinstance(message, basestring):
            message = [message]
 
        client = irc.client.IRC()
        server = client.server()
        factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
 
        c = server.connect(
            SERVER_ADDRESS, SERVER_PORT, LOGIN_NAME, SERVER_PASS,
            connect_factory=factory)
 
        for m in message:
            c.privmsg(LOGIN_CHANNEL, m)
    except Exception as e:
        print('WARN: Failed to send IRC message. (%s)' % e)

7.22.2013

Counting Bits in Python, C++ and Scala (Pt.2)

各プログラミング言語で バイナリファイルの中にある 1 のビットの数をカウントする (その2)

 

こちらのエントリの続き
mog project: Counting Bits in Python, C++ and Scala (Pt.1)

 

C++ 実測 (3)

方法(1)を途中の段階まで実施して配列を横断して加算していく方法。
Hacker's Delight の実装を参考にした。

8ビット単位まで各個計算を行うバージョンを METHOD 3、16ビット単位のバージョンを METHOD 4 としている。

コード

これまでのコードも含め、全体を掲載する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#define METHOD 3
 
#include <fstream>
#include <vector>
#include <string>
#include <ctime>
#include <cassert>
 
using namespace std;
 
int const kChunkSize = 1 << 14;
 
// METHOD 1
int POP(unsigned long long x) {
  unsigned long long const y = 0x3333333333333333ULL;
  x -= (x >> 1) & 0x5555555555555555ULL;
  x = (x & y) + ((x >> 2) & y);
  x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL;
  x += x >> 8;
  x += x >> 16;
  x += x >> 32;
  return x & 0x7f;
}
 
// METHOD 2
int table[1 << 16];
 
void init_table() {
  for (int i = 0; i < (1 << 16); ++i) table[i] = POP(i);
}
 
int POP2(unsigned long long x) {
  return table[x & 0xffff] +
      table[(x >> 16) & 0xffff] +
      table[(x >> 32) & 0xffff] +
      table[(x >> 48)];
}
 
// METHOD 3
int POP_ARRAY(unsigned long long a[], int n) {
  unsigned long long s, s8, x;
  unsigned long long const y = 0x3333333333333333ULL;
 
  s = 0;
  for (int i = 0; i < n; i += 31) {
    int lim = min(n, i + 31);
    s8 = 0;
    for (int j = i; j < lim; ++j) {
      x = a[j];
      x -= (x >> 1) & 0x5555555555555555ULL;
      x = (x & y) + ((x >> 2) & y);
      x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL;
      s8 += x;
    }
    x = (s8 & 0x00ff00ff00ff00ffULL) + ((s8 >> 8) & 0x00ff00ff00ff00ffULL);
    x += x >> 16;
    x += x >> 32;
    s += x & 0xffff;
  }
  return s;
}
 
// METHOD 4
int POP_ARRAY2(unsigned long long a[], int n) {
  unsigned long long s, s16, x;
  unsigned long long const y = 0x3333333333333333ULL;
 
  s = 0;
  for (int i = 0; i < n; i += 4095) {
    int lim = min(n, i + 4095);
    s16 = 0;
    for (int j = i; j < lim; ++j) {
      x = a[j];
      x -= (x >> 1) & 0x5555555555555555ULL;
      x = (x & y) + ((x >> 2) & y);
      x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL;
      x = (x + (x >> 8)) & 0x00ff00ff00ff00ffULL;
      s16 += x;
    }
    x = (s16 & 0x0000ffff0000ffffULL) + ((s16 >> 16) & 0x0000ffff0000ffffULL);
    x += x >> 32;
    s += x & 0xffffffff;
  }
  return s;
}
 
template <typename T, typename U>
double time_it(int iter, int count, T func, U val) {
  double elapsed = 1e30;
  for (int i = 0; i < iter; ++i) {
    clock_t t = clock();
    for (int j = 0; j < count; ++j) {
      func(val);
    }
    clock_t s = clock();
    elapsed = min(elapsed, static_cast<double>(s - t) / CLOCKS_PER_SEC);
  }
  return elapsed;
}
 
int count_bit(string path) {
  int ret = 0;
  unsigned long long x[kChunkSize];
 
  ifstream fin(path.c_str(), ios::in | ios::binary);
  while (true) {
    fin.read(reinterpret_cast<char *>(&x), sizeof(unsigned long long) * kChunkSize);
    if (fin.eof()) break;
#if METHOD == 1
    for (int i = 0; i < kChunkSize; ++i) ret += POP2(x[i]);
#elif METHOD == 2
    for (int i = 0; i < kChunkSize; ++i) ret += POP2(x[i]);
#elif METHOD == 3
    ret += POP_ARRAY(x, kChunkSize);
#elif METHOD == 4
    ret += POP_ARRAY2(x, kChunkSize);
#endif
  }
  fin.close();
  return ret;
}
 
int main(int argc, char *argv[]) {
  string paths[] = {"00.bin", "01.bin", "02.bin", "03.bin", "04.bin", "05.bin",
      "06.bin", "07.bin", "08.bin", "09.bin", "10.bin", "11.bin"};
  string prefix = "../data/";
  int expected[] = {0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
      100000000, 1000000000, 1 << 30};
 
  init_table();
  for (int i = 0; i < 12; ++i) {
    string path = prefix + paths[i];
    assert(count_bit(path) == expected[i]);
    printf("%s: %f sec\n", paths[i].c_str(), time_it(3, 10, count_bit, path));
  }
  return 0;
}

 

実行結果
00.bin: 0.571133 sec
01.bin: 0.563557 sec
02.bin: 0.562696 sec
03.bin: 0.559637 sec
04.bin: 0.563524 sec
05.bin: 0.567378 sec
06.bin: 0.567318 sec
07.bin: 0.563231 sec
08.bin: 0.561556 sec
09.bin: 0.563927 sec
10.bin: 0.566986 sec
11.bin: 0.563692 sec

1 回の処理あたり 約 56ms。テーブル検索版よりも速い結果となった。

 

C++ 実測 (4)

実測(3) の配列横断処理を行う前処理の単位を 8ビットから 16ビットに変更して計測。
コードの1行目を #define METHOD 4 とする

実行結果
00.bin: 0.646128 sec
01.bin: 0.645220 sec
02.bin: 0.640301 sec
03.bin: 0.638049 sec
04.bin: 0.636916 sec
05.bin: 0.634473 sec
06.bin: 0.638034 sec
07.bin: 0.649389 sec
08.bin: 0.642163 sec
09.bin: 0.639769 sec
10.bin: 0.635853 sec
11.bin: 0.637499 sec

1 回の処理あたり 約 63ms。8ビット版よりも遅くなってしまった。

 

 

Scala の並列処理の実測はまた後日。

7.21.2013

mogtype - The Simplest Kana Typing Training Tool

mogtype - 最もシンプルなタイピングソフト

 

かな入力で、さらさら日本語の文章が書けるようになりたい!と最近思っているのだが
英語キーボードでも動く「かな入力」の練習ソフトがなかなか見当たらない。(まぁ相当なニッチ需要だけど) 

という訳で、かな入力に特化した、
コマンドラインで動く簡単なタイピングソフトを Python + curses で作ってみた。

Mac でのみ動作確認済み。

  • GitHub
    mogproject/mogtype
  • ヘルプ
    $ ./mogtype.py -h
    Usage: mogtype.py [options]
     
    Options:
      --version             show program's version number and exit
      -h, --help            show this help message and exit
      -f PATH, --file=PATH  path to the sentence database (default: mogtype.txt)
      -c COUNT, --count=COUNT
                            number of exercises (default: 8)
  • 使用方法

    引数(-f)で指定したテキストファイル(省略時は mogtype.txt)の内容がランダムでお題になる。

    $ ./mogtype.py -f ./mogtype.txt
  • 画面サンプル
    Screenshot 7 21 13 22 12 2
    カーソルで現在入力中の位置を表現。

    Screenshot 7 21 13 22 14 4
    ミスをするとこんな感じ。

    なお、画面上のtypoは修正済み。(Accurancy->Accuracy)
     
  • このファイルを変更すれば、異なるキーマッピングにも対応できる
    mogtype/keymap.py at master · mogproject/mogtype 

 

とりあえず動く状態になったけど、今後随時バージョンアップしていきたい。

 

 

References

ひらがな/カタカナの変換はこちらを参考にさせて頂きました。

7.18.2013

Counting Bits in Python, C++ and Scala (Pt.1)

各プログラミング言語で バイナリファイルの中にある 1 のビットの数をカウントする (その1)

 

目的

128 MB のバイナリファイル中にある 1 のビットの数を知りたい。
なお、128 MB = 2^7 MB = 2^17 KB = 2^27 Byte = 2^30 Bit なので求める値は 0 以上 2^30 以下となる。 

これを、並列処理なしの Python, C++, 並列処理ありの Scala でコードを書き、
手元の Mac で所要時間を調べてみる。

 

おことわり
実施環境も処理内容も、(私自身の勉強のために)とりあえず今作れるもので試しただけですので
言語自体の性能を比較する意図はございません。

  

データ作成

実測の前に、1のビットが n 個含まれるランダムなバイナリデータを予め用意しておく。

これは Python で、bitarray ライブラリを利用して行った。
さらに、大きな bitarray に対して random.shuffle を行うと所要時間が恐ろしく長くなるので
2^12 ビットのサイズのバケットを予めランダムに作っておくことで処理時間を短縮した。

参考:
mog project: Python: Random Partition of Integer 

 

bitarray のインストール

bitarray 0.8.1 : Python Package Index

$ sudo pip install bit array
コード
make_test_file.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import random
import math
import timeit
from bitarray import bitarray
from random_partition import random_partition
 
ARRAY_SIZE = 1 << 30
CHUNK_SIZE = 1 << 12
 
TEST_COUNTS = [
    (0, '00.bin'), (1, '01.bin'), (10, '02.bin'), (100, '03.bin'),
    (1000, '04.bin'), (10000, '05.bin'), (100000, '06.bin'),
    (1000000, '07.bin'), (10000000, '08.bin'), (100000000, '09.bin'),
    (1000000000, '10.bin'), (1 << 30, '11.bin')]
 
 
def make_bitarray(size, count):
    assert(count <= size)
 
    ret = bitarray(size)
    if count == size:
        ret.setall(True)
    else:
        ret.setall(False)
        if count != 0:
            ret[:count] = True
            random.shuffle(ret)
    return ret
 
 
def make_file(count, path):
    # Create buckets.
    num_buckets = int(math.ceil(float(ARRAY_SIZE) / CHUNK_SIZE))
    buckets = random_partition(num_buckets, count, CHUNK_SIZE)
 
    # Write to file.
    with open(path, 'wb') as fh:
        for b in buckets:
            make_bitarray(CHUNK_SIZE, b).tofile(fh)
    print('Created: %s' % path)
 
 
if __name__ == '__main__':
    timeit.__dict__.update(make_file=make_file)
    for t in TEST_COUNTS:
        time = timeit.Timer('make_file(%d, "%s")' % (t[0], t[1])).timeit(1)
        print('%f sec' % time)

 

実行結果

AWS (t1.micro) で一晩コトコト煮込んだデータ。(約7.2時間)
gzip 圧縮して全部で 41 MB。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ python ./make_test_file.py
Created: 00.bin
3.845612 sec
Created: 01.bin
3.803929 sec
Created: 02.bin
3.650161 sec
Created: 03.bin
4.306574 sec
Created: 04.bin
10.518001 sec
Created: 05.bin
47.784271 sec
Created: 06.bin
213.532356 sec
Created: 07.bin
716.523029 sec
Created: 08.bin
4907.577745 sec
Created: 09.bin
8589.099633 sec
Created: 10.bin
11360.644969 sec
Created: 11.bin
30.384724 sec

Python 実測

bitarray の count 関数を使うだけ。

コード
count_bit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import timeit
from bitarray import bitarray
 
 
TEST_COUNTS = [
    (0, '00.bin'), (1, '01.bin'), (10, '02.bin'), (100, '03.bin'),
    (1000, '04.bin'), (10000, '05.bin'), (100000, '06.bin'),
    (1000000, '07.bin'), (10000000, '08.bin'), (100000000, '09.bin'),
    (1000000000, '10.bin'), (1 << 30, '11.bin')]
PATH_PREFIX = '../data/'
 
 
def count_bit(path):
    x = bitarray()
    with open(path, 'rb') as fh:
        x.fromfile(fh)
    return x.count()
 
if __name__ == '__main__':
    for t in TEST_COUNTS:
        path = PATH_PREFIX + t[1]
        assert(count_bit(path) == t[0])
        timeit.__dict__.update(count_bit=count_bit)
        time = min(timeit.Timer('count_bit("%s")' % path).repeat(3, 10))
        print('%s: %f sec' % (t[1], time))

 

実行結果
00.bin: 1.858225 sec
01.bin: 1.860906 sec
02.bin: 1.858306 sec
03.bin: 1.846741 sec
04.bin: 1.853473 sec
05.bin: 1.840856 sec
06.bin: 1.836229 sec
07.bin: 1.852755 sec
08.bin: 1.864767 sec
09.bin: 1.851672 sec
10.bin: 1.839153 sec
11.bin: 1.824429 sec

1 回の処理あたり 約 185ms。
ちなみにそのおよそ半分がファイル読み込みに要する時間。

 

C++ 実測 (1)

まずは普通のカウント方法で。
一度にストリームから読み込むサイズは試行錯誤の末、2^14 (個の long long int) に落ち着いた。

if stream は read() した後で EOFチェックしないと正しい値にならない。

コード
count_bit.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <fstream>
#include <vector>
#include <string>
#include <ctime>
#include <cassert>
 
using namespace std;
 
int const kChunkSize = 1 << 14;
 
int POP(unsigned long long x) {
  unsigned long long const y = 0x3333333333333333ULL;
  x -= (x >> 1) & 0x5555555555555555ULL;
  x = (x & y) + ((x >> 2) & y);
  x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL;
  x += x >> 8;
  x += x >> 16;
  x += x >> 32;
  return x & 0x7f;
}
 
template <typename T, typename U>
double time_it(int iter, int count, T func, U val) {
  double elapsed = 1e30;
  for (int i = 0; i < iter; ++i) {
    clock_t t = clock();
    for (int j = 0; j < count; ++j) {
      func(val);
    }
    clock_t s = clock();
    elapsed = min(elapsed, static_cast<double>(s - t) / CLOCKS_PER_SEC);
  }
  return elapsed;
}
 
int count_bit(string path) {
  int ret = 0;
  unsigned long long x[kChunkSize];
 
  ifstream fin(path.c_str(), ios::in | ios::binary);
  while (true) {
    fin.read(reinterpret_cast<char *>(&x), sizeof(unsigned long long) * kChunkSize);
    if (fin.eof()) break;
    for (int i = 0; i < kChunkSize; ++i) ret += POP(x[i]);
  }
  fin.close();
  return ret;
}
 
int main(int argc, char *argv[]) {
  string paths[] = {"00.bin", "01.bin", "02.bin", "03.bin", "04.bin", "05.bin",
      "06.bin", "07.bin", "08.bin", "09.bin", "10.bin", "11.bin"};
  string prefix = "../data/";
  int expected[] = {0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
      100000000, 1000000000, 1 << 30};
 
  for (int i = 0; i < 12; ++i) {
    string path = prefix + paths[i];
    assert(count_bit(path) == expected[i]);
    printf("%s: %f sec\n", paths[i].c_str(), time_it(3, 10, count_bit, path));
  }
  return 0;
}

 

実行結果
00.bin: 0.821018 sec
01.bin: 0.808333 sec
02.bin: 0.809245 sec
03.bin: 0.809044 sec
04.bin: 0.809715 sec
05.bin: 0.809076 sec
06.bin: 0.813601 sec
07.bin: 0.813362 sec
08.bin: 0.812772 sec
09.bin: 0.808916 sec
10.bin: 0.811492 sec
11.bin: 0.811594 sec

1 回の処理あたり 約 81ms。

 

C++ 実測 (2)

テーブル検索を行うバージョン。

コード
1
2
3
4
5
6
7
8
9
10
11
12
int table[1 << 16];
 
void init_table() {
  for (int i = 0; i < (1 << 16); ++i) table[i] = POP(i);
}
 
int POP2(unsigned long long x) {
  return table[x & 0xffff] +
      table[(x >> 16) & 0xffff] +
      table[(x >> 32) & 0xffff] +
      table[(x >> 48)];
}

 

実行結果
00.bin: 0.608648 sec
01.bin: 0.598598 sec
02.bin: 0.597612 sec
03.bin: 0.601898 sec
04.bin: 0.600394 sec
05.bin: 0.596191 sec
06.bin: 0.595793 sec
07.bin: 0.600928 sec
08.bin: 0.615904 sec
09.bin: 0.622860 sec
10.bin: 0.629954 sec
11.bin: 0.600881 sec

1 回の処理あたり 約 60ms。だいぶ速くなった。

 

 

残り(C++で配列中のビットを累積的に調べる/Scala実測を行う予定)はまた次回。

7.16.2013

Installing tmux with Fabric

Fabric で tmux をインストール

yum でインストールするだけだけど。

install_tmux.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding: utf-8 -*-
"""
Installing tmux.
"""
 
from fabric.api import *
from fabric.decorators import runs_once, roles, task
 
env.user = 'ec2-user'
env.use_ssh_config = True
 
@task
def install():
    """Install tmux."""
    sudo('yum -y install tmux')

これで、寝ている間も安心して AWS に仕事を任せられる。

 

References

tmux 公式

AWS: EC2 Restore Script

AWS: EC2インスタンスをスナップショットから復元するスクリプト

 

スナップショットのリストアに伴う一連の操作をスクリプト化。

デバイスが EBS 1個だけの場合にのみ対応。

コード

aws_restore.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import sys
import subprocess
import time
try:
    import json
except ImportError:
    print('You need python 2.6 or later to run this script.')
    sys.exit(1)
 
 
def usage():
    print('Usage: %s <instance-id> <snapshot-id>' % sys.argv[0])
    sys.exit(2)
 
 
def run_command(*args):
    output = subprocess.check_output(['aws', 'ec2'] + list(args))
    return json.loads(output)
 
 
def get_volume_id(instance):
    assert(len(instance['BlockDeviceMappings']) == 1)
    return instance['BlockDeviceMappings'][0]['Ebs']['VolumeId']
 
 
def get_zone(instance):
    return instance['Placement']['AvailabilityZone']
 
 
def get_device(instance):
    assert(len(instance['BlockDeviceMappings']) == 1)
    return instance['BlockDeviceMappings'][0]['DeviceName']
 
 
def is_running(instance):
    return instance['State']['Code'] == 16
 
 
def is_stopped(instance):
    return instance['State']['Code'] == 80
 
 
def get_instance(instance_id):
    j = run_command('describe-instances', '--instance-ids', instance_id)
    return j['Reservations'][0]['Instances'][0]
 
 
def start_instance(instance_id):
    sys.stdout.write('Starting instance: %s ...' % instance_id)
    run_command('start-instances', '--instance-ids', instance_id)
 
    for i in range(20):
        sys.stdout.write('.')
        time.sleep(10)
        if is_running(get_instance(instance_id)):
            break
    else:
        raise(RunTimeError('Timed out for waiting.'))
    print('OK')
 
 
def stop_instance(instance_id):
    sys.stdout.write('Stopping instance: %s ...' % instance_id)
    run_command('stop-instances', '--instance-ids', instance_id)
 
    for i in range(20):
        sys.stdout.write('.')
        time.sleep(10)
        if is_stopped(get_instance(instance_id)):
            break
    else:
        raise(RunTimeError('Timed out for waiting.'))
    print('OK')
 
 
def detach_volume(volume_id):
    sys.stdout.write('Detaching volume: %s ...' % volume_id)
    run_command('detach-volume', '--volume-id', volume_id)
    print('OK')
 
 
def create_volume(zone, snapshot):
    sys.stdout.write('Creating volume from snapshot: %s ...' % snapshot)
    j = run_command(
        'create-volume', '--availability-zone', zone,
        '--snapshot-id', snapshot)
    print('OK')
    return j['VolumeId']
 
 
def attach_volume(volume_id, instance_id, device):
    sys.stdout.write('Attaching volume: %s ...' % volume_id)
    run_command(
        'attach-volume', '--volume-id', volume_id,
        '--instance-id', instance_id, '--device', device)
    print('OK')
 
 
def delete_volume(volume_id):
    sys.stdout.write('Deleting volume: %s ...' % volume_id)
    run_command('delete-volume', '--volume-id', volume_id)
    print('OK')
 
 
if __name__ == '__main__':
    if len(sys.argv) != 3:
        usage()
 
    instance_id = sys.argv[1]
    snapshot = sys.argv[2]
 
    sys.stdout.write('Checking instance: %s ...' % instance_id)
    ins = get_instance(instance_id)
 
    old_vol = get_volume_id(ins)
    print('OK')
 
    stop_instance(instance_id)
    detach_volume(old_vol)
    new_vol = create_volume(get_zone(ins), snapshot)
    attach_volume(new_vol, instance_id, get_device(ins))
    start_instance(instance_id)
    delete_volume(old_vol)

実行例

$ ./aws_restore.py i-xxxxxxxx snap-XXXXXXXX
Checking instance: i-xxxxxxxx ...OK
Stopping instance: i-xxxxxxxx .......OK
Detaching volume: vol-yyyyyyyy ...OK
Creating volume from snapshot: snap-XXXXXXXX ...OK
Attaching volume: vol-zzzzzzzz ...OK
Starting instance: i-xxxxxxxx .....OK
Deleting volume: vol-yyyyyyyy ...OK

7.15.2013

Python: Random Partition of Integer

Python: ランダムな整数分割の実装

目的

  • 非負整数 n を 和を保ったまま m 個のバケットにランダムに分割したい
  • そのとき、各バケットの内容が上限 k を超えないようにする。
  • 制約
    • 0 <= n < 2^31
    • 1 <= m < 2^19
    • 0 <= k < 2^31
    • n <= m * k
  • 言い換えれば、n, m, k が与えられた時に、以下を満たす数値のリスト x[i] (0 <= i < m) を
    ランダムに求めたい。
    • ∀i (0 <= x[i] <= k)
    • Σ x[i] = n
 

アイデア

二分木と再帰を使ってリストを作る。

動作イメージは以下のとおり。

 Blog 20130715

m = 1 ならばそれは葉なので計算終了。(値は n)
それ以外は、ほぼ半数になるように m を左右に振り分ける。

振り分ける個数を左から順に m1, m2、振り分ける数値を n1, n2 とした場合、制約から

  • a) n1 + n2 = n
  • b) 0 <= n1
  • c) 0 <= n2
  • d) n1 <= m1 * k
  • e) n2 <= m2 * k
を満たす必要がある。
 
e) に a) の n2 = n - n1 を代入すれば、n1 >= n - m2 * k という不等式が導けるので
n1 は以下の制約を持つことになる。 
  • 0 <= n1 <= n    a), b), c) から
  • n - m2 * k <= n1 <= m1 * k    a), d), e) から

この範囲内でランダムに n1 を選択すれば、再帰的に木を構築することができる。

 

なお、木構造ではなく線形に順次リストを作っていく方法も可能だが
再帰にした場合にスタック溢れが起きてしまうこと、また大きな値が前方に偏ってしまうことから採用を見送った。

  

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import random
 
 
def random_partition(size, total, upper):
    assert(1 <= size <= 0x0007ffff)
    assert(0 <= total <= 0x7fffffff)
    assert(0 <= upper <= 0x7fffffff)
    assert(total <= size * upper)
 
    if size == 1:
        return [total]
 
    s1 = size / 2
    s2 = size - s1
    t1 = random.randint(max(0, total - s2 * upper), min(total, s1 * upper))
    t2 = total - t1
 
    return random_partition(s1, t1, upper) + random_partition(s2, t2, upper)
    • 実行例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
print(random_partition(1, 0, 0))
print(random_partition(1, 1, 1))
print(random_partition(10, 1000, 100))
print(random_partition(10, 1, 1))
print(random_partition(7, 10, 10000))
print(random_partition(0x0007ffff, 0x7fffffff, 0x7fffffff)[:10])
print(random_partition(0x0007ffff, 0x7fffffff, 0x7fffffff)[-10:])
print(random_partition(0x0007ffff, 0, 0)[:10])
print(random_partition(10000, 1000000000, 100000)[:10])
print(random_partition(1 << 18, 0, 1 << 12)[:10])
print(random_partition(1 << 18, 1, 1 << 12)[:10])
print(random_partition(1 << 18, 100000000, 1 << 12)[:10])
print(random_partition(1 << 18, 1000000000, 1 << 12)[:10])
print(random_partition(1 << 18, 1 << 18, 1)[:10])
print(random_partition(1 << 18, 1 << 30, 1 << 12)[:10])
    • 出力例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[0]
[1]
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[1, 3, 3, 0, 0, 0, 3]
[16, 14, 5, 2, 5, 3, 4, 0, 1, 0]
[1, 0, 0, 1, 0, 1, 0, 1, 0, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[176, 546, 366, 86, 1032, 169, 28, 155, 518, 188]
[3934, 3915, 4078, 4082, 4095, 4095, 4094, 4095, 4038, 4015]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096]

How to Copy Text in Vim with iTerm

iTerm の Vim 上でテキストをコピーする

デフォルトの状態では、iTerm で Vim 等を開いた時
マウスで範囲を選択してもコピー・ペーストを行うことができない。 

プロファイルのターミナル設定で、「Enable xterm mouse reporting」のチェックを外したら
コピペできるようになった。

Preferences 2

 

追記: 2013-07-16

@todashuta 様より追加情報を頂きました。ありがとうございます。

7.11.2013

How to Send an IRC Message with SSL in Python

Python: SSL 経由で IRC メッセージを送信する方法

  • IRC ライブラリをインストール
    irc 8.3.1 : Python Package Index

    $ sudo easy_install irc

    または

    $ sudo pip install irc
  • 実装例
    この辺りを参考に
    jaraco / irc / source / scripts / ssl-cat.py — Bitbucket
    irc_message.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    # -*- coding: utf-8 -*-
     
    import ssl
     
    SERVER_ADDRESS = 'xxx.xxx.xxx.xxx'
    SERVER_PORT = 6667
    SERVER_PASS = 'xxxxxx'
     
    LOGIN_NAME = 'xxxxxx'
    LOGIN_CHANNEL = '#xxxxxx'
     
     
    def send(message):
        try:
            import irc.client
            import irc.connection
     
            client = irc.client.IRC()
            server = client.server()
            factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
     
            c = server.connect(
                SERVER_ADDRESS, SERVER_PORT, LOGIN_NAME, SERVER_PASS,
                connect_factory=factory)
     
            c.privmsg(LOGIN_CHANNEL, message)
            c.disconnect()
        except Exception as e:
            print('WARN: Failed to send IRC message. (%s)' % e)

チャンネル宛にプライベートメッセージを送ると、メンバー全員へ通知される。

サーバのパスワードを間違えてもExceptionは発生しない。
どうやって気づけばよいのだろう。

 

Related Posts

mog project: Python: Sending IRC Message, Improved

7.09.2013

AWS: More Listings of EC2 Information

AWS: EC2 情報の簡易一覧表示

こちらの改良版。
mog project: AWS: Brief Listing of EC2 Instances 

インスタンスの他に、ボリューム、スナップショットの一覧も表示。

aws_list.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import sys
import subprocess
try:
    import json
except ImportError:
    print('You need python 2.6 or later to run this script.')
    sys.exit(1)
 
 
def list_instances():
    print('Instances:')
    command_args = ['describe-instances']
    headers = [
        ('Name', 16),
        ('Instance', 16),
        ('Type', 10),
        ('Launch Time', 26),
        ('State', 12),
    ]
 
    def generator(output):
        for i in output['Reservations'][0]['Instances']:
            yield [
                get_name(i),
                i['InstanceId'],
                i['InstanceType'],
                i['LaunchTime'],
                i['State']['Name'],
            ]
    print_list(command_args, headers, generator)
 
 
def list_volumes():
    print('Volumes:')
    command_args = ['describe-volumes']
    headers = [
        ('Name', 16),
        ('Volume ID', 16),
        ('Cap', 8),
        ('Type', 11),
        ('Zone', 17),
        ('State', 12),
    ]
 
    def generator(output):
        for i in output['Volumes']:
            yield [
                get_name(i),
                i['VolumeId'],
                '%d GB' % i['Size'],
                i['VolumeType'],
                i['AvailabilityZone'],
                i['State'],
            ]
    print_list(command_args, headers, generator)
 
 
def list_snapshots():
    print('Snapshots:')
    command_args = ['describe-snapshots', '--owner-ids', 'self']
    headers = [
        ('Name', 16),
        ('Snapshot ID', 16),
        ('Cap', 8),
        ('Start Time', 28),
        ('State', 12),
    ]
 
    def generator(output):
        for i in output['Snapshots']:
            yield [
                get_name(i),
                i['SnapshotId'],
                '%d GB' % i['VolumeSize'],
                i['StartTime'],
                i['State'],
            ]
    print_list(command_args, headers, generator)
 
 
def get_name(obj):
    return ''.join(
        [x['Value'] for x in obj.get('Tags', []) if x['Key'] == 'Name'])
 
 
def print_list(command_args, headers, generator):
    # Print header.
    print(''.join([x[0].ljust(x[1]) for x in headers]))
    print('-' * sum([x[1] for x in headers]))
 
    # Run command.
    stdout = subprocess.check_output(['aws', 'ec2'] + command_args)
    output = json.loads(stdout)
 
    # Print result.
    for result in generator(output):
        print(''.join(
            [result[i][:x[1] - 1].ljust(x[1]) for i, x in enumerate(headers)]))
 
    # Print footer.
    print('')
 
 
if __name__ == '__main__':
    list_instances()
    list_volumes()
    list_snapshots()

7.07.2013

How to Set Up IRC Server on CentOS

CentOS で IRC サーバの構築

以下、簡単なメモのみ。

  • 今回選んだサーバは ngIRCd
    ngIRCd: Next Generation IRC Daemon
  • EPEL リポジトリへ接続可能な状態にして yum コマンドでインストール
    EPEL/ja - FedoraProject 
    # yum install ngircd
  • /etc/ngircd.conf を編集
    最新のサンプル(http://ngircd.barton.de/doc/sample-ngircd.conf)を参考にカスタマイズ。
    バージョンアップに伴い、[Limits] や [SSL] が使えるようになっている。 
  • /etc/ngircd.motd を作成
    Message Of The Day。空ファイルでもよい。 
  • 構文チェック
    # ngircd --configtest
     
  • サービス追加、起動
    # chkconfig ngircd --add
    # service ngircd start
     
  • 必要に応じて iptables 等の設定を変更

 

Setup Shogi-server on AWS with Fabric

AWS: shogi-server を Fabric で構築する

目的

コンピュータ将棋のネット対局用サーバ shogi-server を AWS の EC2 インスタンス上に構築する。

このとき、全ての設定作業を Fabric で完結させたい。

 

前提

 

準備するもの

 

iptables

shogi-server (CSAプロトコル) で使う 4081 ポートを許可、ロギング設定など。

iptables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4081 -j ACCEPT
-N LOGGING
-A INPUT -j LOGGING
-A LOGGING -j LOG --log-level warning --log-prefix "DROP:" -m limit
-A LOGGING -j DROP
COMMIT

 

sudoers

sudo の際に環境変数 PATH を引き継ぐようにしないと、なぜか Ruby のインストールに失敗してしまう。
env_keep に PATH を追記した sudoers ファイルをあらかじめ用意しておく。

sudoers (オリジナルとの差分)
# diff -u ./sudoers.20130707 ./sudoers
--- ./sudoers.20130707  2013-05-10 03:53:29.636195742 +0900
+++ ./sudoers   2013-07-07 11:47:48.647961526 +0900
@@ -76,6 +76,7 @@
 Defaults    env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
 Defaults    env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
 Defaults    env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
+Defaults    env_keep += "PATH"
  
 #
 # Adding HOME to env_keep may enable a user to run unrestricted

 

起動スクリプト

shogi-server をサービスとして登録し、起動/停止を行うためのスクリプト。
/etc/init.d/shogi-server として配備。

PIDファイルを /var/run、ログを /var/log/shogi-server 配下に作成している。

サービスは専用ユーザ shogi で実行される。

shogi-server.init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/bin/bash
#
# Start/Stop shogi-server daemon
#
# chkconfig: 2345 85 15
# description: shogi-server daemon mode
 
PROC_NAME=shogi-server
PROC_USER=shogi
PROC_GROUP=shogi
PROC_BIN=/opt/shogi-server/shogi-server
PID_FILE=/var/run/shogi-server.pid
DAEMON_LOG_DIR=/var/log/shogi-server
PLAYER_LOG_DIR=$DAEMON_LOG_DIR/player-logs
CONSOLE_LOG=$DAEMON_LOG_DIR/console.log
 
EVENT_NAME=event1
PORT_NUMBER=4081
 
# Sanity checks
[ -x $PROC_BIN ] || exit 1
 
# Source function library & LSB routines
. /etc/rc.d/init.d/functions
 
RETVAL=0
 
start()
{
    echo -n $"Starting $PROC_NAME: "
 
    mkdir -p $DAEMON_LOG_DIR $PLAYER_LOG_DIR
    touch $PID_FILE
    chown $PROC_USER:$PROC_GROUP $DAEMON_LOG_DIR $PLAYER_LOG_DIR $PID_FILE
 
    su - $PROC_USER -c \
        "ruby $PROC_BIN --daemon $DAEMON_LOG_DIR --pid-file $PID_FILE \
        --player-log-dir $PLAYER_LOG_DIR $EVENT_NAME $PORT_NUMBER \
        > $CONSOLE_LOG 2>&1"
    RETVAL=$?
    if [ $RETVAL -eq 0 ]; then
        success
    else
        rm -f $PID_FILE
        failure
    fi
    echo
}
 
stop()
{
    echo -n $"Stopping $PROC_NAME:"
    if [ -f $PID_FILE ]; then
        killproc -p $PID_FILE $PROC_NAME -TERM
        rm -f $PID_FILE
    fi
    RETVAL=$?
    [ $RETVAL -eq 0 ] && success || failure
    echo
}
 
case "$1" in
    start)
        start
        RETVAL=$?
        ;;
    stop)
        stop
        RETVAL=$?
        ;;
    status)
        status -p $PID_FILE $PROC_BIN
        RETVAL=$?
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        RETVAL=2
        ;;
esac
 
exit $RETVAL

 

logrotate 定義ファイル

ログファイルの洗い替えは logrotate を利用する。
/etc/logrotate.d/shogi-server として配備。

shogi-server.logrotate
1
2
3
4
5
6
7
8
9
/var/log/shogi-server/shogi-server.log
/var/log/shogi-server/player-logs/*.log {
    weekly
    rotate 4
    missingok
    notifempty
    copytruncate
    compress
}

 

Fabric 用スクリプト

Fabric で使う Python スクリプト。
env.hosts はハードコーディングして使ってもよい。

標準APIの put 関数にも sudo 機能があるものの、SELinux 対応やバックアップ作成などが物足りなかったので
独自の _put という関数でラップしている。

EC2 のファイアウォール設定もこのスクリプトに含めているが、分離させた方がいいかもしれない。

shogi-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# -*- coding: utf-8 -*-
"""
Installing shogi-server and its dependencies.
"""
 
import os
import json
from fabric.api import *
from fabric.decorators import runs_once, roles, task
 
env.user = 'ec2-user'
# env.hosts = []
env.use_ssh_config = True
 
WORK_DIR = os.path.dirname(os.path.abspath(__file__))
 
APL_USER = 'shogi'
APL_GROUP = 'shogi'
APL_HOME = '/home/%s' % APL_USER
 
GIT_SHOGI_SERVER = \
 
RUBY_VERSION = '1.9.3-p448'
 
EC2_SECURITY_GROUP = 'quick-start-1'
 
 
@task
def setup():
    """Setup tasks for shogi-server."""
 
    setup_os_user()
    setup_firewall()
    install_ruby()
    install_shogi_server()
 
 
@task
def setup_os_user():
    """Add 'shogi' user and 'shogi' group."""
 
    if sudo('id %s' % APL_USER, quiet=True).succeeded:
        return
 
    # Add user and group.
    sudo('groupadd -g 501 %s' % APL_GROUP)
    sudo('useradd -u 501 -g %s %s' % (APL_GROUP, APL_USER))
 
    # Copy sudoers.
    _put('%s/sudoers' % WORK_DIR, '/etc/sudoers', '440', 'root', 'root')
 
 
@task
def setup_firewall():
    """Configure linux and EC2 firewall.
        AWS CLI and its connection settings are required."""
 
    # Linux iptables
    _put('%s/iptables' % WORK_DIR, '/etc/sysconfig/iptables',
         '600', 'root', 'root')
    sudo('service iptables restart')
 
    # EC2 firewall
    port = 4081
    cidr = '0.0.0.0/0'
    old = local('aws ec2 describe-security-groups', capture=True)
    j = json.loads(old)
 
    found = (EC2_SECURITY_GROUP, port, cidr) in (
        (a['GroupName'], b['FromPort'], c['CidrIp'])
        for a in j['SecurityGroups']
        for b in a['IpPermissions']
        for c in b['IpRanges'])
 
    if not found:
        opts = '--group-name %s --ip-protocol tcp ' % EC2_SECURITY_GROUP
        opts += '--from-port %d --to-port %d --cidr-ip %s' % (port, port, cidr)
        local('aws ec2 authorize-security-group-ingress %s' % opts)
 
 
@task
def install_ruby():
    """Install git, rbenv, ruby-build and ruby."""
 
    # Install git.
    sudo('yum -y install git')
 
    # Install rbenv.
    with cd('/usr/local'):
        sudo('git clone %s %s' % (GIT_RBENV, 'rbenv'))
        sudo('mkdir rbenv/shims rbenv/versions')
 
    # Install ruby-build.
    with cd('/usr/local'):
        sudo('git clone %s %s' % (GIT_RUBY_BUILD, 'ruby-build'))
    with cd('/usr/local/ruby-build'):
        sudo('./install.sh')
 
    # Create system-level profile.
    profile = '/etc/profile.d/rbenv.sh'
    sudo("""echo 'export RBENV_ROOT="/usr/local/rbenv"' > %s""" % profile)
    sudo("""echo 'export PATH="/usr/local/rbenv/bin:$PATH"' >> %s""" % profile)
    sudo("""echo 'eval "$(rbenv init -)"' >> %s""" % profile)
 
    # Install requirements.
    sudo('yum -y groupinstall "Development Tools"')
    sudo('yum -y install openssl-devel readline-devel zlib-devel')
 
    # Install ruby.
    sudo('su - -c "rbenv install 1.9.3-p448"')
    sudo('su - -c "rbenv global 1.9.3-p448"')
    sudo('su - -c "rbenv rehash"')
 
 
@task
def install_shogi_server():
    """Install and start shogi-server as a service."""
 
    shogi_path = '/opt/shogi-server'
 
    if sudo('test -d %s' % shogi_path, quiet=True).succeeded:
        return
 
    # Clone from repository.
    sudo('git clone %s %s' % (GIT_SHOGI_SERVER, shogi_path))
    sudo('chown -R %s:%s %s' % (APL_USER, APL_GROUP, shogi_path))
 
    # Copy init script.
    _put('%s/shogi-server.init' % WORK_DIR, '/etc/init.d/shogi-server',
         '755', 'root', 'root')
 
    # Add and start service.
    sudo('chkconfig --add shogi-server')
    sudo('service shogi-server start')
 
    # Copy logrotate setting.
    _put('%s/shogi-server.logrotate' % WORK_DIR,
         '/etc/logrotate.d/shogi-server', '644', 'root', 'root')
 
 
def _put(src, dst, mode, owner, group):
    tmp = '/tmp/%s.tmp' % src.split('/')[-1]
    backup_opts = '--backup=simple --suffix=.`date +%Y%m%d`'
    permission_opts = '-m %s -o %s -g %s' % (mode, owner, group)
 
    put(local_path=src, remote_path=tmp)
    sudo('install %s %s %s %s' % (backup_opts, permission_opts, tmp, dst))
    sudo('rm -f %s' % tmp)

 

実行方法

fab コマンドで実行。Microインスタンスで20分弱で完了。

$ fab -f ./shogi-server.py --list
$ fab -f ./shogi-server.py -H 対象サーバ setup

7.02.2013

AWS: Brief Listing of EC2 Instances

AWS: EC2 インスタンスの簡易一覧表示

 

AWS CLI の出力結果(json)を表形式にフォーマットして見やすくしたい。

 

EC2 インスタンス一覧の表示

jq でやろうと思ったが断念。Python のスクリプトを書いた。 

aws_list.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import sys
import subprocess
try:
    import json
except ImportError:
    print('You need python 2.6 or later to run this script.')
    sys.exit(1)
 
 
def print_instances():
    headers = [
        ('Name', 24),
        ('Instance', 12),
        ('Type', 12),
        ('State', 12),
        ('LaunchTime', 24),
    ]
    print(''.join([x[0].ljust(x[1]) for x in headers]))
    print('-' * sum([x[1] for x in headers]))
 
    stdout = subprocess.check_output(['aws', 'ec2', 'describe-instances'])
    output = json.loads(stdout)
 
    for ins in output['Reservations'][0]['Instances']:
        result = [
            ''.join([x['Value'] for x in ins['Tags'] if x['Key'] == 'Name']),
            ins['InstanceId'],
            ins['InstanceType'],
            ins['State']['Name'],
            ins['LaunchTime'],
        ]
        print(''.join([result[i].ljust(x[1]) for i, x in enumerate(headers)]))
 
if __name__ == '__main__':
    print_instances()
  • 出力例
Name                    Instance    Type        State       LaunchTime
------------------------------------------------------------------------------------
server1                 i-00000000  t1.micro    running     2013-07-01T10:58:49.000Z
server2                 i-11111111  m1.small    stopped     2013-07-01T11:58:49.000Z
server3                 i-22222222  m1.small    stopping    2013-07-01T12:58:49.000Z
                        i-33333333  t1.micro    pending     2013-07-01T13:58:49.000Z

最終行は名前未設定のインスタンス。