VOYAGE GROUP エンジニアブログ

voyagegroup_techのブログ
VOYAGE GROUPエンジニアブログです。

blktraceによるI/Oトレース

こんにちは. adingoで検索周りの仕事をしているHと申します. 検索エンジンやデータベースなどのディスクI/Oが多いアプリケーションでは, いかにI/Oを抑えるかがパフォーマンスの決め手になります. 今回はアプリケーションが出したI/Oリクエスト一つ一つがどのようにデバイスドライバに渡るかをトレースするblktraceというツールを簡単に紹介します. blktraceはlinux kernel 2.6におけるツールとなっており, 2.6.17-rc1以降ではカーネルにパッチを当てることなく利用可能です. (ツール自体はインストールする必要があります.)

何がトレースされる?

linuxカーネルではI/Oリクエストは多数のレイヤーを通じて実際にデバイスに要求が出されます. ざっくり書くと, アプリケーションから発行されたI/Oリクエストは以下のようなパスを通りデバイスへと辿りつきます. ([...] はカーネルの範囲を表しています.)

アプリケーション -> [ファイルシステム -> ページキャッシュ -> ブロックI/Oレイヤ -> デバイスドライバ] -> デバイス
           

blktraceがトレースするのは, "ブロックI/Oレイヤ"の部分になります. ブロックI/Oレイヤでは, I/OスケジューラによりI/Oリクエストの並び換えや連接ブロックへのI/Oリクエストのマージ等が行われているため, ブロックI/Oレイヤの入口と出口ではI/Oリクエストの順番が異なります. つまり, blktraceはブロックI/Oレイヤの入口と出口, そして内部でのI/Oリクエストの状態をトレースすることができるツールとなります.

インストール

通常は, ディストリビューションのパッケージ管理システムからインストールが可能だと思います. 私が試した範囲では, CentOS(yum), Debian(apt), openSUSE(yast) では可能でした. 一応, CentOS(yum)での例を載せておきます.

% sudo yum install blktrace
            

使い方

私も使い方が詳しい訳ではないので, 一番基本的な使い方のみ紹介します. とりあえずこれだけでも何とかなると思います.

% blktrace -d /dev/sda -o - | blkparse -i > trace.out
            

blktraceは実際にトレースを行うプログラムで, blkparseが見やすく整形してくれるツールとなります. 上のコマンドでは監視するデバイス(上では/dev/sda)とトレースログの出力先(上ではtrace.out)を指定すれば良いことになります. これを実行し, 別のシェルなどでI/Oを発行するプログラムを実行すると, そのプログラムのI/Oをトレースすることができます. 実際には, カーネルやその他の動作中のアプリケーションすべてのI/Oが捕捉されてしまうため, 注意が必要です. (また, ファイルにトレース結果を書き込んでしまうため, その分のI/Oも捕捉されてしまいます. blktraceのオプションで別のマシンにネットワーク経由で転送するモードもあるので, それにより回避できます.)

以下に, 自分のsortした時の出力の一部を載せておきます.

8,0    5        1     0.073660305 21678  A   R 280968474 + 32 <- (8,3) 263971704
8,1    5        2     0.073660507 21678  Q   R 280968474 + 32 [zsh]
8,1    5        3     0.073662783 21678  G   R 280968474 + 32 [zsh]
8,1    5        4     0.073664230 21678  P   N [zsh]
8,1    5        5     0.073665153 21678  I   R 280968474 + 32 [zsh]
8,1    5        6     0.073666419 21678  U   N [zsh] 1
8,1    5        7     0.073667496 21678  D   R 280968474 + 32 [zsh]
8,0    5        8     0.078350794 21678  A   R 280968506 + 8 <- (8,3) 263971736
8,1    5        9     0.078350962 21678  Q   R 280968506 + 8 [zsh]
8,1    5       10     0.078351973 21678  G   R 280968506 + 8 [zsh]
8,1    5       11     0.078352571 21678  P   N [zsh]
8,1    5       12     0.078353001 21678  I   R 280968506 + 8 [zsh]
            

結果は左から, 1:デバイス番号, 2:CPU, 3:シーケンス番号, 4:タイムスタンプ, 5:PID, 6:イベント名, 7:リクエストタイプ, 8:開始ブロック(セクタ番号(LBA)) + ブロック(セクタ)数, 9:プロセス名 となります. どのプロセス(5)のどのブロック(8)のI/Oリクエストが, どの状態であるか(6)を見ることができます. イベント名には, I(I/Oスケジューラのキューに挿入された), D(ブロックI/Oレイヤからデバイスドライバに送られた), C(I/Oが完了した)などがあります. 詳しくはマニュアルをご確認ください.

まとめ

linux kernel 2.6から導入されたI/Oトレースのツールを紹介しました. このツールの出力結果を解析して, カーネルのI/Oスケジューリングアルゴリズムを修正する人は少ないと思いますが, 既存スケジューリングアルゴリズムやカーネルのパラメータを調整する場合などは一つの指標になると思います. また, 内部の動作を見てみるのは単純に面白いものです. blktrace自体ができることや, 詳しい使い方は以下のマニュアルやプレゼン資料を見てください.

あなたにも今日こそPerlの文字化けが理解できるたった一つの原則

こんにちは。ECナビラボの須藤(@ajiyoshi)と申します。

唐突です が、Perl(5.8以降)で文字化けに悩んだことはありませんか?私はあります。

そして、よくわからないまま"Perl 文字化け"などで検索すると、次のような単語が洪水のごとく溢れていて、なんだかよく分からなかったりしないでしょうか。

  • 'flagged UTF8'
  • Encode::decode
  • Encode::decode_utf8
  • Encode::encode
  • Encode::encode_utf8
  • 'Wide character in print at hoge.pl line xx.'
  • use utf8
  • open my $fh, '<:encoding(utf8)', $file
  • binmode
  • 「入り口で decode、内部でflagged UTF8、出口でencode」
  • utf8::is_utf8
  • utf8::upgrade
  • utf8::downgrade
  • utf8::encode
  • utf8::decode

さらになんだかよくわからないままEncode::decodeしたりEncode::encodeしたりutf8::is_utf8で調べた りuse utf8をつけたりはずしたりしてるうちになんか動くようになったりして分からないままそのままにしていませんか?

この記事は そういう方のために書かれています。今回こそは、ちゃんと理解してみませんか。

内部文字列とUTF8バイナリは違う

Perl で文字化けしないために必要なことは、ほぼこの原則だた1点に集約されます。

内部文字列とUTF8バイナリをきちんと区別する

「ちょっと待って!」あなたは言います。「その内部文字列とかUTF8バイナリとかってのは一体何?」

この文書で使う用語の定 義:

内部文字列
内部文字列とはPerlが内部的に保持している文字列の表現のこと。普通は'flagged UTF8'と呼ばれる。
UTF8バイナリ
UTF8バイナリとは文字列をUTF8エンコーディングにより表現したバ イナリ列のこと。

Perlの内部文字列の表現としてUTF8が採用されていることからか、ユニコード文字列として特別扱いを 受ける内部文字列は'flagged UTF8'と呼ばれます。特にPerlのユニコード事情に詳しくない人にとって、この呼び方はエンコーディ ング方式としてのUTF8そのものや、UTF8 エンコードされたバイナリ列と混同しやすいと私は思います。その上、Perlが内 部文字列をどのように表現しているのかについて、ほとんどの場合 プログラマが知る必要はないのです。無用な混乱を避けるため、この文書では「内部文字列」と呼ぶことにします。

内部文字列とUTF8バイ ナリは全く異なります。Perlはいまやユニコードを知っており、内部文字列の文字境界を知っています。length 関数に内部文字列を渡すと、文字列に含まれる文字の数が返ります。一方でlength関数にUTF8バイナリを渡すと、それがバイナリとして何バイトで表 現されるかを返します。例えば「ECナビ」は4文字ですが、それをUTF8で表現すると8バイト必要になります。

内部文字列とUTF8 バイナリは比較できません。論理的には同じ文字列を表現しようとしている内部文字列とUTF8バイナリを eq で比較しても結果は偽です。

内 部文字列とUTF8バイナリを結合しようとするのもよくないアイディアです。違うものなのだから当然ですね。sjisなデータとeuc-jp なデータをナイーブに結合しようとするとどうなるでしょうか?普通は文字化けします。内部文字列とUTF8バイナリを結合しようとすると同じように文字化 けします。おそらくあなたが目にするPerlの文字化けの半分くらいはこれが原因です。

Encode::decode - 内部文字列を作り出す

ではその内部文字列というのはどうすれば得られるのでしょうか。

それには Encode::decodeを使います。

CやJava風の型記法でシグネチャを表現すると次のようになります。

 String Encode::decode(Encoding enc, byte[] bin);
String Encode::decode_utf8(byte[] bin);

文字コードencでエンコーディングされたバイナリ配列binから、内部文字列を作り出すのがdecode()の役割です。あらかじめエンコー ディングがUTF8であると決まりきっている場合もあるため、UTF8バイナリから内部文字列を作り出す decode_utf8 も用意されています。

 use strict;
use warnings;
use Encode qw(decode_utf8);

my $binText = "ECナビ";
my $strText = decode_utf8($binText);

warn length($binText);
warn length($strText);

このコードをUTF8で保存して実行すれば、すでに述べたlengthの挙動を確かめることができます。

Encode::encode - 内部文字列を特定のエンコーディングのバイナリ表現に変換する

内部文字列というのはPerl内部でのお話です。現実の要件では EUC-JPやshiftjis、UTF8など、特定のエンコーディングを使っ てテキストを書き出したり、DBに格納する必要があるでしょう。これにはEncode::encodeを使います。

 byte[] Encode::encode(Encoding enc, String str);
byte[] Encode::encode_utf8(String str);

UTF8で書いたコードで、出力はEUC-JPで行う例を示します。

 use strict;
use warnings;
use Encode qw(decode_utf8 encode);

my $binText = "ECナビ";
my $strText = decode_utf8($binText);

#出力はEUC-JPにしたい
print encode('euc-jp', $strText);

use utf8 とは何か。

use utf8とは何でしょうか?Perlのドキュメントには「スクリプトがUTF8で書かれていることをPerlに伝えるためだけに使え」とかスピリチュアル なことが書いてあります。

そういうものだとして、Perlにそれを伝えるとPerlはどう振舞うのでしょうか?

大きく 2点、おまけが1点あります。

  • 文字列リテラルはもはやバイナリ列ではなく内部文字列を作り出す
  • 正規表現リテラルが ユニコードを認識する
  • (おまけ)変数名などの識別子にユニコードを使える
  •  use strict;
    use Encode;

    #こいつはUTF8バイナリ
    my $binPrice = '1,234円';

    {
    use utf8;
    # 文字列リテラルは内部文字列を作り出す
    my $strPrice = '1,234円';
    # 正規表現リテラルがユニコードを認識する
    $strPrice =~ tr/0-9,/0-9,/;
    print Encode::encode_utf8($strPrice);
    }

    おまけについては特に述べません。そういうのを好む人もいれば、忌み嫌う人もいるでしょうから。

    出力ではエンコーディングを明 示しよう

    内部文字列とUTF8バイナリは違うことを覚えたあなたは文字化けを見ることも減り、use utf8 を使えばいちいちdecodeしなくても内部文字列を得られてだいぶ幸せになりました。

    ところがPerlがこんな文句を言う事があるのに 気づきました。この警告は何を意味しているのでしょうか?

    'Wide character in print at hoge.pl line xx.'

    これは、典型的には内部文字列をそのままprintに渡した場合に起きる警告です。

     use utf8;
    #'Wide character in print at hoge.pl line xx.'
    print '代表取締役丹野修一';

    内部文字列をPerlがどのように表現しているかについて、あなたが(賢明なことに)何も知らないのであれば、こんなことをしようとは思わない でしょう。だってuse utf8しているあなたのコードでは、いまや文字列リテラルは内部文字列であり、それが物理的にどういう具合に表現されているか知らないのだから、そのま まprintしたところでそれをコンソールとかDBとかブラウザとかの他の誰かが読むことができるなんてありえそうもないことです。

    書 き出すというのは、誰かに読んでもらうことを期待しているのだから、あなたと読み手との間で合意が取れているその文字エンコーディングを Perlに教えてあげるべきです。

     use utf8;
    use Encode;
    #UTF8でエンコーディングすることになっている
    print Encode::encode_utf8('代表取締役丹野修一');

    何も指定しなければ、readやprintや<STDIN>などはバイナリのまま読み書きしようとします。つまり、read や<$fh>は(内部文字列ではなく)バイナリ列を返すし、printには適切なエンコーディングで符号化されたバイナリ列を渡すべきです。 そうじゃないと、10年前に書かれていまや誰も中身を把握していないスクリプト(そいつはエクセルCSVに何かぞっとするようなことをして、データを EUC-JPに変換した上でDBに何かしてるみたいで、もちろんuse strictなんてないし、Jcode.pmがどうとか書いてあります)が、Perlを新しくした瞬間に動かなくなってしまいそうです。

    チー ム全員がPerlの文字列について正しく理解しているのでない限り私はおすすめしませんが、「このファイルハンドルから読み取るデータは必 ずUTF8なので、読み取ったデータを自動的に内部文字列に変換してほしい」「このファイルにはEUC-JPで書き出すので、内部文字列をprintした ら自動的にEUC-JPに変換した上で書き出して欲しい」などとPerlに伝えることができます。

     use strict;
    use warnings;

    my $path = shift;
    #open時にエンコーディングを指定する
    open(my $fh, '<:encoding(utf8)', $path) or die;
    #内部文字列
    my $strLine = readline($fh);
    close($fh);

    STDIN、STDOUTのようなすでに開いているファイルハンドルに同じことをするときは、binmode関数を使います。これもチーム全員 がPerlの文字列について正しく理解しているのでない限り混乱の元だと私は思いますので、サンプルコードは割愛します。

    まとめ

    ここまでくれば、モダンPerlの文字列に関する一般則、

    「入口でdecode、自分のプログラムでは内部文字列を使い、出口で encode」

    の意味も理解できたのではないでしょうか。decodeは文字コードの妥当性を検証した上で内部文字列を作りだしますの で、入り口で decodeしてやれば例えば冗長なUTF8符号化による微妙な脆弱性も防ぐことができます。

    落穂ひろい

    To use utf8 or not utf8.

    あなたはuse utf8すべきでしょうか?

    use utf8の隠れた副作用として、"あなたのチームのPerlの文字列についての理解度を試す"があると私は思います。ちゃんと分かってないと、たいてい嫌 なことがおきます。

    チームのPerl錬度が低いなら、「基本的にはuse utf8せずにどうしても必要な部分だけuse utf8する」というのもひとつのポリシーではないかと思います。

    iPad で「妄撮」をやってみました。

    こんにちは。 ECナビの子会社adingoでエンジニアをしています tatenosystem です。
    圧倒的ダメエンジニアなので、本ブログでは「ダメなネタ担当」になると思います。

    私は、先月アメリカ出張に行ったタイミングで iPad を買ってきました。
    そこでいち早く社内で iPad で遊んでいると、システムの偉い人から「iPadをネタに何かブログ書いて」という ムチャぶり 御神託がありました。

    いい機会なので、HTML5 の canvas を使って iPad向けのページを作ってみようと思います。
    私は「HTML5非対応ブラウザ」が横行しているこの世の中、HTML5 を避けていたのですが
    iPad 専用ページということであれば、HTML5 が必ず使えるので安心です。

    HTML5 canvas と iPad のタッチパネルを使った素敵なネタは……

    妄撮!!!


    グラビア界に革命を起こした「妄撮」を iPad に移植しましょう。
    いや~、我ながら「発想がダメ」ですね。。。
    いつか他の優秀なエンジニアさんに怒られる日がくると思います。

    早速 JavaScript を書いていきます。
    実をいうと似たようなプログラムを大昔 Java Applet で書いたことがあるので、それを移植すればOKです。

    iPad のJSタッチイベントは iPhone とまったく同じです。
    タッチしながら指を動かすと"touchmove"イベントが発生します。
    ですので、以下のようにイベントリスナーに登録します。

      document.addEventListener("touchmove", clearBlock, false);

    iPad 以外の HTML5 対応ブラウザでも利用できるように、通常のマウスイベントも登録しておきます。

     window.onmousemove = clearBlock;
     window.onmousedown = function(){ moveFlag = 1; }
     window.onmouseup = function(){ moveFlag = 0; }


    あとは、自作の clearBlock 関数内でタッチした部分の一部画像を違う画像に置き換えてあげればOKです。

    では、早速動かしてみましょう。
    大昔描いたイラストとか使いたかったのですが、どう考えても会社のブログでは NG なので(^^;)
    我が家のソファに居座っている「物体」に被写体になってもらいました。

    01


    で、さっそくタッチして画像を切り取ってみます。

    02


    おお!ちゃんと「妄撮」できた!

    いかん、面白い!!!


    ちなみに、動画だとこんな感じです。



    有名な絵師がコレやって公開してくれると、一気に iPad が(間違った)普及するかもしれませんねw

    サンプルURL は以下です。
    iPad 及び HTML5対応のブラウザをお持ちの方は是非遊んでみてください。

    http://tatenosystem.com/ipad/canvas/

    なかなか面白かったので、暇を見つけて今後拡張していきます。
    今回は15分位でプログラムを書いたのでかなり適当です。
    (むしろ「ぬいぐるみ」の撮影の方が時間がかかってしまいました)

    次回はもう少し真面目なネタに挑戦します。
    記事検索
    QRコード
    QRコード