Hatena::ブログ(Diary)

Memo

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, tags=None, version=None):
        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 用のクライアントを作ったけど、これはまた別の機会に。

[] 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, tags=None, version=None):
        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 用のクライアントを作ったけど、これはまた別の機会に。

2014-09-30

[] ここ最近のお仕事

デスマってるけど、新しいものを試せたり知見を得た。箇条書きでメモ。


Ansible + Serverspec でサーバ構築を自動化した

  • 自動化便利。テストも書いたら安心感が得られた
  • Serverspec 動かす際に spec_helper.rb を少し変えたけど、ここを変えるだけで、いい感じに動くの良い
  • 最初は Ansible の assert でやろうとしたけど、オートメーションツールが何であれ、単体で動くServerspec の方が性に合った
  • ただ他の開発者(特に Windows)に Ansible を課すのは厳しい
  • Golang だったら楽なんだろうなと思った

仕事で Slack を使うようになった

JAX-RS(Jersey)を使った

  • 過去案件で使った Jersey ベースの FW にしたけど、ORM が無くてクエリをゴリゴリ書くパターン
  • 素直に ORM 探して使えば良かった
    • Java の ORM って結局何が良いの?
  • というか DropWizard にすれば良いのか
  • ユニットテストDB の取得した結果とかを Excel の表と一致するとかが書かれていた
    • 想像以上に辛い
    • 開発にバイナリファイルを多用するのは良くない
    • 少なくとも JSON にすれば一括で置換とか出来るのに
    • JavaFactory Girl 的な物はないんかな
  • Setter/Getter は IDE で自動で作るから良いけど、コメントをいちいち書かないと行けないのは辛い
    • Lombok 使えば良かった
    • すっかり存在を忘れてた orz
  • 作りが悪いのかテストや開発サーバ実行時に全て読み込むから起動が遅くて辛かった
  • プライベートで作ってる Python の方は遅くても数秒で起動するけど、数十秒起動に掛かるのは辛い
  • JSON Schema を使った
    • バリデーションもこれ
    • API がどういうリクエストを受け付けるか分かりやすくなったので便利

マイグレーションツールに Flyway を使った

平行して担当している保守案件のせい^H^Hおかげで JVM の設定とかに少しずつ詳しくなってきた

  • ストップ・ザ・ワールド辛い
  • 本当に数十秒止まるんだな
  • 元々一人でやってた人が退職したため、引き継いだけどチームの体制作りからやった
  • チームで回せるようにして、そうなってきた
  • 属人性を無くすため全てチケット管理
    • 問い合わせを全てチケット化
    • お客さんに返信する内容は全てチケットに紐づけてレビューを徹底
      • チーム全員の OK が出たら返信を出す
    • ナレッジの共有もチケットに
    • Git で週の振り返りを共有
  • まだ手動でやってるので自動化出来る所は自動化していきたい
  • ドキュメントがほぼないので、問い合わせのたびにコードを見るが、再現とかの作業は出来るだけ若者に振るようにした
  • ちゃんと手を動かしてくれるから良い感じ
  • Tomcat の ログをパースしてグラフ化するツールをチームのメンバーに作ってもらった
    • 言語は何でも良かったけど Golang に興味があるって言ってたので、Golang で作ってもらった
    • ログをビジュアライズ化されて、負荷の状況とか分かって大変良い(グラフは Excel)
    • 自分がさっと作れるの言語は Python だけど、チームのツールの言語は Golang にしていきたい

案件が複数並列で稼働したためデスマったけど色々知見が得れた。

平穏な生活に早く戻りたい…。

2014-08-23

[] html なファイルを開いて行挿入をすると意図しないコマンドが実行される

GitHubvim-jp に issue として登録したら速攻で問題の場所を教えてもらった。

filetype indent on を設定し行挿入を行うと意図してないコマンドが実行される ? Issue #614 ? vim-jp/issues ? GitHub


これの何が困るかって言うと、今まで F に FizzyFinder の File モードを割り当てていて、F でランチャー的に現在のディレクトリを開いて他のファイルを開くというのをずっとやっている*1


で、最新の MacVim Kaoriya を使っていて HTML を編集して、o を押した際にエラーが発生して編集していたものが全て消えるという自体に遭遇した。

色々追いかけて再現手順が判明したから、vim-jp に投げてみたら、すぐに問題の場所が分かった。


結局、~/.vim/indent/ に html.vim をコピーしてパッチを当てた。

Vim は組み込みのプラグインとかも自分の所に置くとそれで上書きしてくれるので便利。


追記:

修正を送って頂いたのが、速攻で取り入れられた。

Runtime file updates. ? fd2995b ? vim-jp/vim ? GitHub

次の MacVim Kaoriya のリリースに含まれるまではパッチでしのごう。

*1:F を殺していてその利便性をそこねているのは承知している。元々自分の使いかたとして後ろに戻る動きはあまりやらないし、使いたい場合は T{移動したい文字}h とかしてる。

2014-08-09

[] Py-Autodoc 0.3 リリースした

主に仕事で必要になりそうと思ったので、requests に対応した。


仕事で JavaAPI サーバを書いていて、応答が JSON のみで、

リクエストとレスポンスを自動でドキュメントに出して楽をしたかったので、

以前作った Py-Autodoc を使おうとした。


自分で作っといてアレだけど、いざ使おうとして API サーバPython しかダメなのに気づいた。

API サーバPython に限定する理由はないので、requests を使って、取得したレスポンスをドキュメントにするようにした。

requests のレスポンスに送信した requests オブジェクトが格納されてるので実現できた。


こんな感じで書く。

import requests

class TestUnittest(TestCase):
  def setUp(self):
    self.client = requests

  @classmethod
  @autodoc.generate('var/test_unittest.rst')
  def tearDownClass(cls):
    pass

  @autodoc.describe('POST /')
  def test_post(self):
    """ POST / """
    params = {'id': 1, 'message': 'foo'}
    headers = {'content-type': 'application/json'}
    res = self.client.post('http://example.com/',
                           data=params, headers=headers)
    self.assertEqual(res.status_code, 200)

    return res

後は実際に使ってみて不満点を直そうと思う。。

2014-07-27

[][] socket.io-go-emitter を試してみる

SocketIO のサーバー側が Node.js で以下のようなコード。

var io = require('socket.io').listen(9000);
var logger = require('./logger');
var redis = require('socket.io-redis');

io.adapter(redis({ host: 'localhost', port: 6379 }));

io.sockets.on('connection', function (socket) {
  logger.info('Connected from' + socket.id);

  socket.on('disconnect', function () {
    logger.info('disconnect');
  });
});

logger.info('Start Socket.IO server');
logger.info('Listening localhost:9000');

ブラウザ側。

var socket = io.connect('http://localhost:9000');
socket.on('msg', function (data) {
  console.log(data);
});

クライアント側の Golang なコード。

package main

import (
    "github.com/yosuke-furukawa/socket.io-go-emitter"
)

func main() {
    emitter, _ := SocketIO.NewEmitter(&SocketIO.EmitterOpts{
        Host: "127.0.0.1",
        Port: 6379,
    })
    emitter.Emit("msg", "I love you!!")
}

Golang を go run client.go として実行すると、ブラウザ側に I love you!! というメッセージが表示された。

てっきり一回 Node.js 側(上のコード方)で受信して、そこからブラウザにブロードキャストするもんかとおもってたけど、ちょっと動きが違って少しビックリした。


今までこういう事をやろうとすると、maccman/juggernaut ? GitHub を使ってたけど、juggernaut は DEPRECATED になってたので、SocketIO 自体でサポートしてくれて良かった。


最初 Node.js の代わりに Golang の SocketIO 1.0 を使おうと思ったが、CORS はデフォルトでサポートしてないみたいなので、とりあえず Node.js で試した。

CORS が必要なのはメインで動作させるのが Flask で SocketIO は別ポートで動かしたいから。

2014-07-26

[] CentOSsudo yes でエラー

メモ。

Ansible で CentOS6.4 に対して以下のコマンドを実行しても転送されない。

hosts/development

[development]
192.168.1.10
$ ansible-playbook -i hosts/development site.yml -k -c paramiko
PLAY [development] ************************************************************ 
GATHERING FACTS *************************************************************** 
<192.168.1.10> REMOTE_MODULE setup

これでタイムアウト

サーバー側に /var/log/secure を見ると、sftp を使おうとしてる。

というわけで、

ansible.cfg scp_if_ssh = True と transport = ssh を追加。

[ssh_connection]
ssh_args = -o ForwardAgent=yes
scp_if_ssh = True

[defaults]
host_key_checking = False
transport = ssh
$ ansible-playbook -i hosts/development site.yml -k -c ssh -vv
PLAY [development] ************************************************************ 
GATHERING FACTS *************************************************************** 
<192.168.1.10> ESTABLISH CONNECTION FOR USER: ansible_user
<192.168.1.10> REMOTE_MODULE setup
<192.168.1.10> EXEC ['ssh', .....

接続は確立されたけど、今度は sudo が正しく動作してないよう。

Ansible 1.2.2 hangs on playbook with sudo: yes ? Issue #3881 ? ansible/ansible ? GitHub

--ask-sudo-pass

を付けろと。


というわけで、最終的に以下のコマンドで正しく動作した。

$ ansible-playbook -i hosts/development site.yml -k -c ssh -vv --ask-sudo-pass

2014-07-23

[] Node.js の logger

どれ使えばいいのか良く分からない。

log4js を使えばいいのか。

適当にググったら winstone ってのを見つけたので試してみる。


var env = process.env.NODE_ENV || 'dev';
var winston = require('winston');
 
winston.setLevels(winston.config.syslog.levels)
 
if (env !== 'dev') {
  winston.remove(winston.transports.Console);
}
 
var customColors = {
  trace: 'white',
  debug: 'green',
  info: 'green',
  warn: 'yellow',
  crit: 'red',
  fatal: 'red'
};
 
var logger = new (winston.Logger)({
  colors: customColors,
  levels: {
    trace: 0,
    debug: 1,
    info: 2,
    warn: 3,
    crit: 4,
    fatal: 5
  },
  transports: [
    // Console.
    new (winston.transports.Console)({
      level: 'trace',
      colorize: true,
      timestamp: true
    }),
    // File.
    new (winston.transports.File)({
      filename: './logs/node.log',
      level: 'info',
      timestamp: true,
      json: false,
      maxsize: 100000,
      maxFiles: 5,
    })
  ]
});
winston.addColors(customColors);
 
module.exports = logger;

app.js

var logger = require('./logger');
logger.trace('trace');
logger.debug('debug');
logger.info('info');
logger.warn('warn');
logger.crit('crit');
logger.fatal('crit');
$ node app.js
2014-07-23T15:48:33.023Z - trace: trace
2014-07-23T15:48:33.026Z - debug: debug
2014-07-23T15:48:33.027Z - info: info
2014-07-23T15:48:33.027Z - warn: warn
2014-07-23T15:48:33.028Z - crit: crit
2014-07-23T15:48:33.028Z - fatal: crit

trace とか debug の文字の所に色が付いて表示された。

logs/node.log にもちゃんとログが出力された。

時間が +9:00 されてない感じがするが、とりあえずこれを使ってみよう。