ちょっと詳細なCPUモニタ

/proc/statを読み込んでCPU使用率をモニタリングする@Linux
ちょっと詳細で、userで消費している時間、system、nice、iowait、irq、softirq、steal、guest で別々に数値を取れる。個々の意味はヨクワカラナイ ^^;


実行結果はこんな感じ:

non-idle : 0.710000
user     : 0.410000
system   : 0.255000
nice     : 0.000000
iowait   : 0.000000
irq      : 0.000000
softirq  : 0.045000
steal    : 0.000000
guest    : 0.000000

がんばってネットワーク通信するプログラムを動かしている最中で、user, system, softirqが上がっている。
1秒おきに更新される。


プログラム:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

static int pexit(const char* msg)
{
    perror(msg);
    exit(1);
}

typedef unsigned long long cpu64_t;
struct cpu_t {
    cpu64_t user;
    cpu64_t nice;
    cpu64_t system;
    cpu64_t idle;
    cpu64_t iowait;
    cpu64_t irq;
    cpu64_t softirq;
    cpu64_t steal;
    cpu64_t guest;
};

static void read_stat(struct cpu_t* cpu)
{
    char buf[256];
    int fd = open("/proc/stat", O_RDONLY, 1);
    if(fd < 0) { pexit("open"); }
    if(read(fd, buf, sizeof(buf)) < sizeof(buf)) { pexit("read"); }
    close(fd);

    memset(cpu, 0, sizeof(struct cpu_t));
    sscanf(buf,  "cpu  %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu",
            &cpu->user, &cpu->nice, &cpu->system, &cpu->idle, &cpu->iowait,
            &cpu->irq, &cpu->softirq, &cpu->steal, &cpu->guest);
}

int main(void)
{
    struct cpu_t before;
    read_stat(&before);

    setvbuf(stdout, NULL, _IONBF, 0);

    while(true) {
        usleep(1*1000*1000);

        struct cpu_t now;
        read_stat(&now);

        cpu64_t total =
            (now.user    - before.user) +
            (now.system  - before.system) +
            (now.nice    - before.nice) +
            (now.idle    - before.idle) +
            (now.iowait  - before.iowait) +
            (now.irq     - before.irq) +
            (now.softirq - before.softirq) +
            (now.steal   - before.steal) +
            (now.guest   - before.guest);

        printf("non-idle : %lf\n", ((double)(total - now.idle + before.idle)) / ((double)total));
        printf("user     : %lf\n", ((double)(now.user    - before.user    ))  / ((double)total));
        printf("system   : %lf\n", ((double)(now.system  - before.system  ))  / ((double)total));
        printf("nice     : %lf\n", ((double)(now.nice    - before.nice    ))  / ((double)total));
        printf("iowait   : %lf\n", ((double)(now.iowait  - before.iowait  ))  / ((double)total));
        printf("irq      : %lf\n", ((double)(now.irq     - before.irq     ))  / ((double)total));
        printf("softirq  : %lf\n", ((double)(now.softirq - before.softirq ))  / ((double)total));
        printf("steal    : %lf\n", ((double)(now.steal   - before.steal   ))  / ((double)total));
        printf("guest    : %lf\n", ((double)(now.guest   - before.guest   ))  / ((double)total));
        printf("\e[9F");

        before = now;
    }
}

RDMA+9pまたはNFSで超高速・超低負荷なストレージサーバー

RDMA(Remote DMA)の話は:http://d.hatena.ne.jp/sdyuki/20090115/1232030259
リモートホストのメモリにCPUを介さず直接値を書きこむので、CPUへの負荷が非常に小さく、かつ極めて小さい遅延で通信できることが期待できる。
Broadcom NetExtreme II BCM5706 は TCP/IP + TCP Offload Engine でRDMAをサポートしたGbE NICで、手頃なお値段で買える。


9Pは元々Plan9で作られたリソース共有プロトコルで、Linuxに移植されている。NFSのようにリモートホストファイルシステムをマウントできる。
9Pはトランスポート層から独立していて、たとえばTCP/IPトランスポート層や、KVMの上のゲストOSからホストOSの通信に特化したトランスポート層などが使えるらしい(参照:Paravirtualized File Systems)。
サーバーの実装はここ:http://npfs.svn.sourceforge.net/viewvc/npfs/npfs/trunk/


で、他にどんなトランスポート層がサポートされているのか探してみたら、RDMAをサポートしているらしい:http://lxr.linux.no/linux+v2.6.29/net/9p/trans_rdma.c

というわけでNetExtreme IIと9pを使えば、非常に高速でCPU負荷も極小なストレージサーバーをお安く作れるのではないか。


うーん…でも定額給付金では買えないお値段。もうちょっと安くなるとお遊びで買えるなぁ。


LinuxNFSのコードの中にも「rdma」という単語が入っていた。NFS(と言うかSun RPCか)でもRDMAをサポートしていそう。十分安定しているし、サーバー側もカーネルモードだから9pより速いかな。RDMAも現実味を帯びてきた感じ。

double-hash-spaceの挙動

s1 s2 s3 s4
rhs whs 再配置方法 rhs whs 再配置方法 rhs whs 再配置方法 rhs whs 再配置方法
1.初期状態 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4
2.s4ダウン 1,2,3 1,2,3 1,2,3 1,2,3 1,2,3 1,2,3 x x
3.再配置開始 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 ->1,2,3,4
4.s3ダウン 1,2 1,2,4 1,2 1,2,4 x x 1,2,4
5.再配置開始 -> 1,2,3,4 1,2 ->1,2,3,4 -> 1,2,3,4 1,2 ->1,2,3,4 -> 1,2,3,4 ->1,2,3,4 -> 1,2,3,4 ->1,2,3,4
6.再配置完了 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4
s1 s2 s3 s4
rhs whs 再配置方法 rhs whs 再配置方法 rhs whs 再配置方法 rhs whs 再配置方法
1.初期状態 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4
2.s4ダウン 1,2,3 1,2,3 1,2,3 1,2,3 1,2,3 1,2,3 x x
3.再配置開始 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 ->1,2,3,4
4.s3ダウン 1,2,4 1,2,4 1,2,4 1,2,4 x x 1,2,4 1,2,4
5.再配置開始 -> 1,2,3,4 1,2,4->1,2,3,4 -> 1,2,3,4 1,2,4->1,2,3,4 -> 1,2,3,4 ->1,2,3,4 -> 1,2,3,4 1,2,4->1,2,3,4
6.再配置完了 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4

ノードが落ちたときに、どうやってハッシュ表を配るか。1,2,3,4は全ノードがactiveな状態で、1,2,3はs4にfaultフラグが付けられている。
再配置を行うときに、rhsでfaultフラグが付いていないノードは、すべての担当データを持っていると扱われる。担当データを持っているノードのうちもっともアドレスが小さいノードから、担当データを持っていないが持っているべきノードへとデータがコピーされる。


問題は再配置が完了する前にノードが落ちた場合のハッシュ表の配り方。前者は「fault状態のノードを伝播する」方法で、後者は「ハッシュ表を上書きする」方法。
5.でもう一度再配置を開始するとき、比較元が異なる。後者では中断された再配置の再配置先はactiveなノードなノードとして比較される。前者では正常に再配置が完了した場合にのみ再配置先はactiveになる。


後者の方が正しい気がするけど、後者にするとテストでエラーになる。逆に前者だとうまくいく。


※後者訂正
ノードがダウンしたとき、ハッシュ表はManagerから配れれるが、Managerのrhsからfaultフラグが取り外されるのは、再配置のコピーフェーズがすべてのノードで正常に完了したときのみ。途中で中断するとManagerのrhsは更新されない。
よって後者の4.s3ダウンのときに配られるrhsでは、s4はfault状態のまま。従って正しくは↓

s1 s2 s3 s4
rhs whs 再配置方法 rhs whs 再配置方法 rhs whs 再配置方法 rhs whs 再配置方法
1.初期状態 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4
2.s4ダウン 1,2,3 1,2,3 1,2,3 1,2,3 1,2,3 1,2,3 x x
3.再配置開始 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 1,2,3->1,2,3,4 -> 1,2,3,4 ->1,2,3,4
4.s3ダウン 1,2 1,2,4 1,2 1,2,4 x x 1,2 1,2,4
5.再配置開始 -> 1,2,3,4 1,2 ->1,2,3,4 -> 1,2,3,4 1,2 ->1,2,3,4 -> 1,2,3,4 ->1,2,3,4 -> 1,2,3,4 1,2->1,2,3,4
6.再配置完了 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4 1,2,3,4

スケールアウトする分散ブロックデバイスのアイディア

DRBDみたいだけどノードを足すとスケールアウトする感じのもの。ローカルのHDDよりリモートのメモリの方が速いという前提に基づく。RDBMSやファイルサーバーのバックエンドとして。
RDBMSをkey-valueストレージでは置き換えられない状況は少なからず存在する。そんな場合にブロックデバイスレベルでスケールアウトさせることで、CPUとネットワークがボトルネックにならない限りまで性能を向上させることを目論む。


共有するデータのサイズは固定長で、TCPのブロックサイズくらいのサイズで分割し、先頭から 0, 1, 2, ... とインデックスを振る。
サーバーノードの数も固定する。(インデックス mod サーバーノードの数)番目のノードにデータを保存し(ストライピング)、完全に均等に分散されるようにする。
(インデックス mod サーバーノードの数)+1 番目 と +2 番目のノードにもデータをレプリケーションする。レプリケーションは数珠つなぎで行い、スループットを稼ぐ。数珠つなぎだと遅延が増えるわけだが、+1番目のノードにコピーした段階でクライアントにレスポンスを返してしまえば遅延は短縮できる。


サーバーノードを増やしたくなったら、ちょっとread/writeを止めて、一斉にデータを移動させる。移動が終わったら再開。しかしちょぃと工夫すればデータの移動中でもread/writeできるようにすることは可能なんじゃないか。


ネットワークの設計ではクライアントは1台しかいないのがポイント。どうせ1台からしかmountできないのだから問題ない(RedHatの方のGFSとかOCFS2はとりあえず脇に)。ほぼC/Sモデルで設計できる。
サーバーは単にクライアントからの要求に応えるだけ。操作はsaveとreadとmove(moveはサーバー追加時用)。saveされていない領域をreadされたら、単に0埋めのデータを返す。
saveはデータ本体と、レプリケーション先のサーバーノードのアドレスを受け取る。自分に書き込みつつ、レプリケーション先にもデータを投げる。
どのサーバーに書き込み、どのサーバーにレプリケーションするかは、すべてクライアントが計算する。
(脇からread-onlyでmountできると用途が広がりそうだけど、誰かがwriteしているファイルシステムを他のマシンがmountするのはread-onlyでも無理が気もする。ファイルシステム的な問題。それをやるならNFSで共有するとか)


クライアントが落ちたとき、クライアントを復旧できないと困る。そこでクライアントからサーバーにフラグを持たせられるようにすると良いのではないか。あなたはアクティブなサーバーで何番目のサーバーですよ、といったフラグをクライアントから書き込む。サーバーはそのフラグの意味を知っている必要は無く、単に保存し、要求に応えて返信すればいいだけ。つまり簡単なkey-valueストレージ機能を持っていればいい。
復旧したクライアントはサーバーの持っているフラグを参照して、ノードリストを復元する。


クライアントはNBDプロトコルのサーバーを実装すればカーネルからブロックデバイスとして認識できる。ATA over Ethernetでもいいかもしれなが、AoEではブロックサイズを変えられなかったような。ブロックサイズを大きくした方がスループットは向上する。


サーバーのストレージの実装は単なるファイルでいい。save要求を受け取ったら、そのままの位置にそのままデータを書けばいい。そうすると自然にスパースファイルになるから、ディスク容量を無駄に消費することはない。穴の領域からreadすると0が帰ってくるのも都合がいい。


以上設計に穴もありそうだけど、実装できそうだし動きそう。


発想としては、ローカルのメモリとローカルのHDDというキャッシュ階層の間にリモートのメモリを追加しているだけに過ぎない。バッテリーバックアップ付きのRAIDカードに巨大なキャッシュを載せてドーピングしたり、SSDを間に挟めば一緒じゃないかとも思う。その方がCPU負荷も低いし。
とりあえずアイディアだけ。

INTEROP Tokyo 2009 クラウドコンピューティングコンペティション

http://www.interop.jp/pavilion/ccc.html
2009年4月30日参加応募締切

実行委員の一人の首藤さんのブログより:

こんな感じのネタ、どうでしょう。

  • memcached (日記12/14分,12/7分) 等 key-value store をチューン / 改造して、無改造品より高い性能を出す。
  • 自前の key-value store (日記2/20分,11/1分) や分散データベース。
  • ...

http://www.shudo.net/diary/2009mar.html#20090326

Chukanにユニットテストフレームワークを統合してみる

perl界でよく使われているらしいproveで実行できるユニットテストフレームワークを書いてChukanに統合してみた。理由は?と聞かれれば Chukan::Test が作りたかった!Ruby的にはRSpecを使っておけばいい気がする。
見た目的にはperlのTest::BaseのRuby移植版、かも知れない。
分散システムのテストで書きたくなる手続き型のテストと、データ駆動型のテストを混ぜて書ける。


テストケースの例:

#!/usr/bin/env ruby
require 'chukan'
include Chukan::Test  # テスト開始

test "load mylibrary" do   # test "テスト名" do
  require "mylibrary"      #   真を返すハズのブロック
end                        # end

run {|b|                   # __END__ 以下のYAMLドキュメントを繰り返し
  test b.name do
    b.score <= 100
  end
}

# mainの終了と同時にテスト終了
__END__
---
name:  test A
user:  a-san
score: 10
---
name:  test B
user:  b-san
score: 100


実行例:

$ prove 01_example.rb
01_example....
01_example.rb:6:in `require': no such file to load -- mylibrary (LoadError)
	from  01_example.rb:6
	from  01_example.rb:5
Finished in 0.028043 seconds.

3 tests, 3 assertions, 0 failures, 1 errors
01_example....FAILED test 1                                                  
	Failed 1/3 tests, 66.67% okay
Failed Test   Stat Wstat Total Fail  Failed  List of Failed
-------------------------------------------------------------------------------
01_example.rb                3    1  33.33%  1
Failed 1/1 test scripts, 0.00% okay. 1/3 subtests failed, 66.67% okay.

"mylibrary"などというライブラリは無いのでテストが1つ失敗している。


ちなみに普通に実行すると:

$ ruby 01_example.rb 

not ok 1 - load mylibrary
01_example.rb:6:in `require': no such file to load -- mylibrary (LoadError)
	from  01_example.rb:6
	from  01_example.rb:5

ok 2 - test A

ok 3 - test B

1..3
Finished in 0.044268 seconds.

3 tests, 3 assertions, 0 failures, 1 errors

メッセージにはそこそこカラフルに色が付いていて、地味なテストに華を与える。

分散システムのテストの自動化

複数のプロセスが相互に連携して動くシステムのテストを自動化したい。ありがちに書くとプロセスを起動すると終了するまで待ってしまうので、同時に複数のプロセスを起動できなくて困る。バックグラウンドで起動させておいて後で終了させたとき、 ps aux | grepしてPIDを調べて、しばらくポーリングして本当に終了するまで待つ、とか面倒すぎる。

あと起動してからしばらくは初期化処理をやっているので、標準出力に「started」と表示されるまで待ちたかったりする。

問題はプロセスをバックグラウンドで起動させると、そのプロセスの扱いが面倒すぎるところ。そこを上手く扱えれば、シグナルを飛ばしたり、標準出力に「started」と表示されるまで待ったりするのは造作もないハズ。

そこでプロセスをオブジェクトとして扱えるようにすればいいという案。

mgr = Process.new("cmdline")
srv1 = Process.new("cmdline")
srv2 = Process.new("cmdline")

mgr.stdout_join("started")
srv1.stdout_join("started")
srv2.stdout_join("started")

ctl = Process.new("cmdline")
ctl.join

mgr.stdout_join("finished")

test = Process.new("cmd")
test.stdout_join("start")

srv1.kill

test.join

ついでにリモートホスト上のプロセスも透過的に扱いたい。sshでコマンドを起動して…とかやればいいかな。シグナルを飛ばすときはそのホストにログインしてからkill。


※追記
Process.newとか長いから、適当なmoduleのメソッドでラップしておいて、先頭でincludeすれば短くてイイ感じ。

include HogeModule
mgr = spawn("cmdline")