ラベル Ruby の投稿を表示しています。 すべての投稿を表示
ラベル Ruby の投稿を表示しています。 すべての投稿を表示

2012年7月31日火曜日

ftp を使ってディスク丸ごとバックアップ

最近、カーネルやOSSソース, ISO イメージの収集蓄積を行うのに、ZFS on Linux を利用しています。 メイン PC のディスクを丸ごとバックアップすることも利用目的の1つです。

しかし、ネット上にいくつも情報があるように、ZFS on Linux + NFS アクセスする場合、どうにも性能が出ないようです。
わたしの環境では、書き込みスループットが 1 MB/s を下回るくらいです。とても実用にはなりません。GigaBit Ether で接続しており、ftp ならば 50MB/s くらいは出る環境なのに、NFS だと全然遅くなってしまうのです。残念。バックアップ対象の PC のディスクは 180GB であり、圧縮して 100GB になったとしても、1 MB/s ですから、100000 秒もかかる計算です。どうにかできないものか・・・

ディスク丸ごとバックアップするというと、次のように dd で NFS 領域へ書き込みというのが、わたしのお決まりのパターンなのですが。。。
# dd if=/dev/sda bs=1M | lzop -c > /mnt_nfs/sda.dd.lzo

ftp ならスループット 50MB/s 出るのだし、なんとかして ftp に dd + lzop の出力を渡して転送できないの?・・・とサーチしてみると、なんと超簡単ではありませんか!無知でした。
ftp> bin
200 Type set to I
ftp> put |"dd if=/dev/sda1 bs=1M | lzop -c" sda1.dd.lzo
local: |dd if=/dev/sda1 bs=1M | lzop -c remote: sda1.dd.lzo
227 Entering Passive Mode (192,168,xxx,xxx,128,22).
150 Opening BINARY mode data connection for sda1.dd.lzo
196+1 records in
196+1 records out
205599744 bytes (206 MB) copied, 11.0407 seconds, 18.6 MB/s
226 Transfer complete.
90549924 bytes sent in 11 seconds (8e+03 Kbytes/s)
なんと、Linux (に限らずかな?) の ftp コマンドでは、put の第一引数の先頭に | を指定すれば、パイプラインからデータを読み取ってくれるのですね。作った人に感謝。

2012-08-01追記
上のコンソールは、VMware 上での実験結果ですが、実際 180GB のディスクバックアップを実行してみました。
[root@livedvd Desktop]# ftp mypc
Connected to mypc (10.xx.yyy.zzz).
220 (vsFTPd 2.2.2)
Name (mypc:centoslive): user01
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> bin
200 Switching to Binary mode.
ftp> put |"dd if=/dev/sda bs=1M | lzop -c" user01.sda.backup-2012-08-01.lzo
local: |dd if=/dev/sda bs=1M | lzop -c remote: user01.sda.backup-2012-08-01.lzo
227 Entering Passive Mode (10,xx,yyy,zzz,uuu,vvv).
150 Ok to send data.
171705+1 records in
171705+1 records out
180045766656 bytes (180 GB) copied, 3891.19 s, 46.3 MB/s
226 Transfer complete.
127003147717 bytes sent in 3.89e+03 secs (32638.60 Kbytes/sec)
対象の PC は、普段は Windows XP が動いているマシンなのですが、CentOS 6.3 LiveDVD を利用してバックアップを行いました。Live 状態では、ftp と lzop が未導入なので、yum でインストールする必要がありました。
LiveDVD は初めて使いましたが、こういったケースでは便利なものですね。

結果として、圧縮後の転送レートは 32MB/s で、約1時間でバックアップが完了しました。
これは、わたしとしては十分に満足できる結果です。

2012-08-03追記
1時間でバックアップできるなら十分かと思いましたが、多少工夫の余地がありましたので、追試を行いました。
まず、圧縮率が低い (180G:127G) のは、ファイルシステム (NTFS) の空き領域に過去に書き込んだデータの痕跡があるためと考えられるので、空き領域にオールゼロデータを書き込んでからバックアップを実行すれば、もっと時間短縮できそうでしたので、次のような Ruby スクリプトを使って、ゼロ書き込みを行いました。Linux なら dd if=/dev/zero of=zerofile とすればいいわけですが、Windows XP なので、一筆。
#!/usr/bin/env ruby
#
# Name: zerofill.rb
#

f = open("zerofile", "wb")

z = [0].pack("N")

@z4 = []

for i in 0...1024
        @z4.push(z)
end

z4096 = @z4.join('')

begin
        while true
                f.write(z4096)
        end
end
なお、Windows XP 上の Ruby スクリプト実行には、Rumix を利用しています。

加えて、前回の実験の際に top で挙動を見ていたら、dd でディスクデータ読み出しを行うと、buffers の値が搭載物理メモリサイズの限界まで上昇し、メモリ回収のため kswapd カーネルスレッドが頻繁に動いていました。
でも、この場合は無駄な動きと思えますので、dd に iflag=direct オプションをつけて実行してみました。結果は次の通り。
ftp> put |"dd if=/dev/sda iflag=direct bs=1M | lzop -1 -c" user01.sda.backup-2012-08-02.lzo
local: |dd if=/dev/sda iflag=direct bs=1M | lzop -1 -c remote: user01.sda.backup-2012-08-02.lzo
227 Entering Passive Mode (10,xx,yyy,zzz,uuu,vvv).
150 Ok to send data.
171705+1 records in
171705+1 records out
180045766656 bytes (180 GB) copied, 2297.55 s, 78.4 MB/s
226 Transfer complete.
40910990817 bytes sent in 2.3e+03 secs (17806.37 Kbytes/sec)
期待どおり、約38分で完了しましたし、バックアップファイルのサイズもコンパクトになりました。

2011年11月13日日曜日

Ruby からシステムコールを直接呼び出す方法

Ruby からシステムコールを直接呼び出す方法です。自分用のメモ。

perl には syscall() が用意されており、使ったことがありましたので、Ruby にもあるかな?とライブラリリファレンスを参照してみると、案の定ありました。
clock_gettime(CLOCK_MONOTONIC) を呼び出すサンプルです。
#!/usr/bin/env ruby

NR_clock_gettime = 228  # defined in asm-x86_64/unistd.h
CLOCK_MONOTONIC = 1     # CLOCK_MONOTONIC defined in linux/time.h
ts = ' ' * 16
syscall(NR_clock_gettime, CLOCK_MONOTONIC, ts)
(tv_sec, tv_nsec) = ts.unpack("l!l!")
printf "%d.%09d\n", tv_sec, tv_nsec

次が実行結果です。
# ./monotonic.rb ; cat /proc/uptime 
134772.112499324
134772.11 134476.00
なお、一般に、システムコール番号はアーキテクチャ毎に異なります。上記の 228 は、x86_64 の場合です。

■関連記事
経過時間の計測方法

2011年11月5日土曜日

経過時間の計測方法

最近、スクリプト言語の恩恵により、C で書く機会というのは滅多にないのですが、ふと経過時間測定のための bash 用 builtin を書きたいと思い、その基礎として、現在の Linux で利用できるインタフェースについて調べました。

わたしの過去の C プログラミング経験 (某UNIX,10年くらい前) では、経過時間の計測には gettimeofday() を使っていました。たぶんそれしか無かった (?) ので。
しかし、その場合、システム時間が遡ってしまう可能性 (システム管理者が日時設定する時など) を考慮する必要があります。
2011-11-06追記、省みると、その当時,その UNIX でも times() はあったでしょうから、わたしが無知だったということですね。wrap への考慮は必要としても。

調べてみると、現在の Linux であれば、clock_gettime(CLOCK_MONOTONIC,) を使えば良さそうだと知りました。
このシステムコールを使えば、システム起動からの経過時間を高精度で得られるようです。
つまり、(カーネルソースまで調べてないですが、おそらくは) /proc/uptime の左側数字の情報源を高精度で得られます。
これなら、システム時刻が遡るタイミングであっても、経過時間を高精度で計測できます。
よく、times() システムコールの戻り値を、経過時間の計測に使ってバグっている (wrap のタイミングで) のを見かけますが、こちらを使えば、そんなバグも作らずに済みそうです。(Solaris やら HP-UX も対応しようとして、共通に使えるものを探した結果そうなるのでしょうけど … )

以下が、サンプルプログラムです。コンパイル時には、librt をリンクするため、リンクオプション -lrt を付加する必要があります。
/*
    gcc -o monotonic monotonic.c -lrt
*/
#include <time.h>
#include <stdio.h>

main()
{
        struct timespec tp ;
        clock_gettime(CLOCK_MONOTONIC, &tp) ;
        printf("%llu %llu\n", tp.tv_sec, tp.tv_nsec) ;
}
実行結果は次の通りです。
# cat /etc/redhat-release 
CentOS release 5.7 (Final)
# uname -a
Linux xxx 2.6.18-274.3.1.el5 #1 SMP Tue Sep 6 20:13:52 EDT 2011 x86_64 x86_64 x86_64 GNU/Linux
# gcc -o monotonic monotonic.c -lrt
# ./monotonic ; cat /proc/uptime 
10714 102582089
10714.10 10559.35
このように、/proc/uptime の値が高精度で得られている様子が見えます。

2011-11-06追記
実際に、clock_gettime(CLOCK_MONOTONIC) を呼び出す bash ビルトインを自作してみました。次の記事を参照ください。

ビルトインを自作して、bash で処理時間をミリ秒単位で計測

2011-11-12追記
ソースから、/proc/uptime と clock_gettime(CLOCK_MONOTONIC) の情報源が同じであることを確かめました。参照したのは、kernel-2.6.18-274.el5 です。
まず、/proc/uptime は次のようになっています。
     97 static int uptime_read_proc(char *page, char **start, off_t off,
     98                                  int count, int *eof, void *data)
     99 {
    100         struct timespec uptime;
    101         struct timespec idle;
    102         int len;
    103         cputime_t idletime = cputime_add(init_task.utime, init_task.stime);
    104 
*   105         do_posix_clock_monotonic_gettime(&uptime);
    106         cputime_to_timespec(idletime, &idle);
    107         len = sprintf(page,"%lu.%02lu %lu.%02lu\n",
    108                         (unsigned long) uptime.tv_sec,
    109                         (uptime.tv_nsec / (NSEC_PER_SEC / 100)),
    110                         (unsigned long) idle.tv_sec,
    111                         (idle.tv_nsec / (NSEC_PER_SEC / 100)));
    112 
    113         return proc_calc_metrics(page, start, off, count, eof, len);
    114 }
"fs/proc/proc_misc.c"
105行目の do_posix_clock_monotonic_gettime() は、もう名前からして、clock_gettime(CLOCK_MONOTONIC) に対応していそうですが、その通りです。
    114 #define do_posix_clock_monotonic_gettime(ts) ktime_get_ts(ts)
"include/linux/time.h"
    157 /*
    158  * Call the k_clock hook function if non-null, or the default function.
    159  */
*   160 #define CLOCK_DISPATCH(clock, call, arglist) \
    161         ((clock) < 0 ? posix_cpu_##call arglist : \
    162          (posix_clocks[clock].call != NULL \
    163           ? (*posix_clocks[clock].call) arglist : common_##call arglist))
...
    218 /*
    219  * Get monotonic time for posix timers
    220  */
    221 static int posix_ktime_get_ts(clockid_t which_clock, struct timespec *tp)
    222 {
*   223         ktime_get_ts(tp);
    224         return 0;
    225 }
    226 
    227 /*
    228  * Initialize everything, well, just everything in Posix clocks/timers ;)
    229  */
    230 static __init int init_posix_timers(void)
    231 {
    232         struct k_clock clock_realtime = {
    233                 .clock_getres = hrtimer_get_res,
    234         };
    235         struct k_clock clock_monotonic = {
    236                 .clock_getres = hrtimer_get_res,
*   237                 .clock_get = posix_ktime_get_ts,
    238                 .clock_set = do_posix_clock_nosettime,
    239         };
    240 
    241         register_posix_clock(CLOCK_REALTIME, &clock_realtime);
*   242         register_posix_clock(CLOCK_MONOTONIC, &clock_monotonic);
    243 
    244         posix_timers_cache = kmem_cache_create("posix_timers_cache",
    245                                         sizeof (struct k_itimer), 0, 0, NULL, NULL);
    246         idr_init(&posix_timers_id);
    247         return 0;
    248 }
...
    920 asmlinkage long
    921 sys_clock_gettime(const clockid_t which_clock, struct timespec __user *tp)
    922 {
    923         struct timespec kernel_tp;
    924         int error;
    925 
    926         if (invalid_clockid(which_clock))
    927                 return -EINVAL;
*   928         error = CLOCK_DISPATCH(which_clock, clock_get,
    929                                (which_clock, &kernel_tp));
    930         if (!error && copy_to_user(tp, &kernel_tp, sizeof (kernel_tp)))
    931                 error = -EFAULT;
    932 
    933         return error;
    934 
    935 }
"kernel/posix-timers.c"
モノトニック時刻の実装詳細は、ktime_get_ts() の先にありますが、この記事では情報源の確認までにします。jiffies_64 に帰着するのかと思ってましたが、そうでは無いようでした。興味のある方は、先を追ってみては。
以上から、10ミリ秒精度で十分であれば、モノトニック時刻の取得は、/proc/uptime の読み出しで代用できるということが確かめられました。各種スクリプト言語から、簡単に利用できて便利と思います。
なお、主要スクリプト言語について、clock_gettime() を呼び出すインターフェースがあるか調べてみました。以下。

perl
http://perldoc.perl.org/Time/HiRes.html

Ruby
見つけられませんでした。2011-11-13追記、Ruby にも perl と同様な syscall() が用意されていますので、強引に呼び出すことはできました。

Python
http://stackoverflow.com/questions/1205722/how-do-i-get-monotonic-time-durations-in-python
同様にして、ほとんど何でも C ライブラリを呼び出せそうです。すごいな Python は。
perl でも、システムコールであれば、syscall() を使って無理すれば呼べますが、こんなにキレイにはできません。2011-11-13追記、実際動かしてみると、CentOS 5 の python 2.4 では動かず、CentOS 6 の python 2.6 なら動きました。CentOS 5 では ctypes が足りませんでした。
2012-10-14追記、CentOS 5.8 で python-ctypes が追加されていたようです。

Ruby については、システムコール直呼び出しはやったことないので不明です。今度、調べてみよう。


■関連記事
bash で処理時間を 10 ミリ秒単位で計測する方法
ビルトインを自作して、bash で処理時間をミリ秒単位で計測
perlでサブルーチンを動的に定義しようとしてハマりました

2011年2月28日月曜日

Rubyの例外処理の落とし穴

(わたしの中では)使用頻度が低い Ruby を使ってスクリプトを書いていて、例外処理で初歩的な間違いを起こしましたので、メモしておきます。
begin
        ...
rescue  Errno::EPIPE => e
        ...
ensue
        temp.close(true)
end
とても初歩的なミスなのですが、ensure と書くべきところを ensue と間違って入力したために、必ず削除されるつもりの一時ファイルが残るというバグでした。このように間違えると、ensue は、rescue 節の一部と解釈されるので、rescue 節が走行する場合にしか、エラーにはなりません。

2011年2月9日水曜日

Rubyには++演算子が無いと気づきました

(わたしの中では)使用頻度が低い Ruby を使ってスクリプトを書いていたら、++演算子が無いことに気がつきました。
                        ...
     53                 try_count = 0
     54                 begin
     55                         try_count++
     56                         gz = Zlib::GzipReader.wrap(workf)
     57                 rescue  Zlib::GzipFile::Error => e
     58                         if try_count < 3
     59                                 retry
     60                         else
     61                                 STDERR.puts "WARNING: "+e.message
     62                         end
     63                 ensure
                        ...
上のようなコード(gzip圧縮ファイルを直接扱う処理)を書いていたのですが、これを実行すると次のようなエラーになり、
xxx.rb:56: undefined method `+@' for #<Zlib::GzipReader:0x7f134cbb98b8> (NoMethodError)
56行目を間違っている??と思って、なんのこっちゃとアタフタすること1時間くらい。。。 もしかして++がいけないのかなと、try_count +=1 にしたらエラーが出なくなりました。 Ruby には、++ないんだねー! 知りませんでした。

2011-03-15追記
なぜなのかについては、下記を参照。
http://okwave.jp/qa/q4634346.html
人気ブログランキングへ にほんブログ村 IT技術ブログへ