2011/02/26

Ruby::ゾンビプロセスを量産するopenメソッドの使い方 - IO::popenと%x{}の違い

DTIサーバのディスク容量監視にyadaemon.rbを使ったRubyスクリプトを走らせています。

ディスク容量を把握するのは、ちょっと面倒なので内部では手を抜いてopen("|df")を実行しています。

しばらく走らせてみたらdefunctプロセス(いわゆるゾンビプロセス)が大量発生したので、その原因を考えてみました。

問題のあったコード

"df"プロセスが終了していなかったのは次の部分です。

問題のあるコード部分抜粋

module YaWatchDisks
  def yield_df_perms
    open("|df ").each_line do |line|
      #       "Filesystem"  "1K-blocks"    "Used"    "Available" "Use%" "Mounted on"
      # perms: ["/dev/md0", "484041584", "453132936", "6514352", "99%", "/"]
      perms = line.split(/\s+/)
      yield perms if perms.length == 6 and perms[1].to_i > 0 and perms[2].to_i > 0 and perms[3].to_i > 0
    end
  end
...

考えてみれば、each_lineメソッドを実行する主体はeach_lineメソッドのブロックを終了してからcloseメソッドを呼び出すなんていう責任はないわけです。

内部ではopen("|df")の部分がGCの対象になるまで常に滞留していたはずです。

でも実際にはGCの対象にもならず、元プロセスは常駐していますから、プロセスが大量に残っていたという事になります。

問題の修正

変に省略せずに、ブロックをつけて呼び出してあげれば、何の問題もなくcloseされるようになります。

修正したコード部分抜粋

module YaWatchDisks
  def yield_df_perms
    open("|df ") do |f|
      f.each_line do |line|
        #       "Filesystem"  "1K-blocks"    "Used"    "Available" "Use%" "Mounted on"
        # perms: ["/dev/md0", "484041584", "453132936", "6514352", "99%", "/"]
        perms = line.split(/\s+/)
        yield perms if perms.length == 6 and perms[1].to_i > 0 and perms[2].to_i > 0 and perms[3].to_i > 0
      end
    end
  end
...

しばらくしてからmuninで確認すると、プロセス数が純増していたグラフからzombie分が消えていて解決したことが確認できました。

ruby-forum.comで見つけた理由らしきもの

Googleでドキュメントを検索していたらhttp://www.ruby-forum.com/topic/62435で、外部プログラムの結果を得るなら「バッククォートや%x{}構文を使ったら?」という記述がありました。

これを、試してみると古いコードでも確かにゾンビプロセスは発生しなくなり、問題なく動くようになりました。

2011/02/25

RDFaを理解する際に便利かもしれないCheatSheetの作成

手持ちのWebサイトではしばらく前からXHTML+RDFa-1.0をDTD宣言に使用していましたが、十分に対応できている状況ではありませんでした。

今は個人的使うCMSというか静的HTML/RSS生成ツールを作成していて、RDFの構造を意識しようとしています。

RDFaをちゃんと使うために神崎さんの「セマンティックHTML/XHTML」を読み直していて、RDFグラフの典型的な型をちゃんと理解しないといけないなぁと思ったところです。

CMSみたいなツールを考えてみると、文書自体の構造はある程度自動化できる可能性がありそうなのですが、文書に含める画像、アンカーなどのリソースは手動でrel, propertyなどの属性を付与する必要があると強く思っています。

コンテンツを書くために毎回本を開くのも大変なので、自分の理解の範囲でまとめた図を作ってみました。

RDFグラフの基本形

RDFa関連の資料を読むためには、リソースとリテラルを区別することが必要です。

Turtle構文が理解できる事は必要ですが、それは神崎さんの本を呼んでもらう事にして、次のような図に対応するTurtle構文RDF/XML構文XHTML+RDFa構文を理解できれば一つの目安になるかなと思います。

RDFaグラフの基本形

最後の形は閉じたRDFの形ですが、RDFaではまだ記述する事ができません。

詳細はRDFa Working Group WikiのContainersAndCollectionsに詳しく載っています。

XHTML+RDFaからのグラフへの変換

太字になっているのは属性に指定されている値のことです。 ここではrdf:typeだけが小文字で理解を補助するために付与しています。

XHTMLからRDFaグラフへの変換

relはリソース用、propertyはリテラル用

ここには書いていませんが、XHTMLのlink, metaタグも、指定する対象がリソースかリテラルかで使い分けています。

さいごに

これで網羅できているわけではないですが、リソース、リテラルの区別と、空白ノードが出現するタイミングを理解することがとりあえずの目標です。

おそらくこれぐらいは覚えておかないと、RDFaを意識したタグ付けはストレスな作業になるでしょう。

自動化できそうなのは、ダブリンコアやFOAFで定義されているタグの種類を提示したり、それがリソース用なのかリテラル用なのか判別するところぐらいでしょうか。

この記事で取り上げた品々

2011/02/22

Ruby 1.9.2以降(移行後?) - YAML::unescapeの行方

作成しているアプリケーションの中で設定を変更するために、YAMLデータ形式を経由してsave/restore機能を付けています。

便利に使っていたのですが、やや本気で使ってみたところ、UTF-8な日本語文字列をファイルに書き出したタイミングでエスケープされた形式になってしまう事に気がつきました。

YAML形式でファイルを書き出すsave_yamlモジュールメソッド

  def save_yaml(file, hash)
    require 'syck/encoding'
    open(file, "w") do |f|
      f.write(YAML::dump(hash))
      f.flush
    end
  end
  module_function :save_yaml

次のように、出力の一部は人間が読み書きできる形式ではなくなっています。

...
title: "\xE3\x83\x97\xE3\x83..."
...

日本語に限らずヨーロッパ系言語も1バイトに収まらないものはあるので、困っている人は他にも居たようで、調べてみるとYAML::unescape()を使う workaround をみつけました。→ Ruby to_yaml utf8 string

しかしRuby 1.9.2移行はYAML関連のパッケージが syck.rb にまとめられてしまったので、モジュール名がSyckになっています。

結局はSyck::unescapeを使う事で無事に解決しました。

変更後のモジュールメソッド

  def save_yaml(file, hash)
    require 'syck/encoding'
    open(file, "w") do |f|
      f.write(Syck::unescape(YAML::dump(hash)))
      f.flush
    end
  end
  module_function :save_yaml

2011/02/06

Ubuntu 10.04 LTSでlibreofficeが文字化けする件について

わりと普通にPPAをみてUbuntu 10.04 LTS (Lucid) 用のlistファイルを作成してLibreOffice 3.3を導入したところ見事に文字化けしてしまいました。

LibreOfficeの起動画面に日本語が正しく表示されないところ

cat /etc/apt/sources.list.d/libreoffice.list の出力結果

deb http://ppa.launchpad.net/libreoffice/ppa/ubuntu lucid main 
deb-src http://ppa.launchpad.net/libreoffice/ppa/ubuntu lucid main
$ curl http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x83FBA1751378B444 | sudo apt-key add  -
$ sudo apt-get update
$ sudo apt-get purge 'openoffice*.*'
$ sudo apt-get install libreoffice libreoffice-help-ja

実際はlibreoffice-gnomeを入れたんですが、原因はlibreoffice-gtkパッケージが抜けていたことのようでした。

ダブルクォートのオートコレクトについて

前々回にOpenOfficeを使っていてダブルクォートがスペシャルキャラクタに変換されてしまう挙動についての記事を投稿しました。

LibreOfficeで調べてみたところ、どうもデフォルトではReplaceにチェックは入っていないようです。

Windows版とか、マック版とかに依存しない挙動である事は確認していますが、言語環境とかにも影響されるのか、ちょっとなぞです。

さいごに

最近のPPAパッケージの導入手順はadd-apt-repositoryコマンドを使うのが一般的なようです。 やっている事は/etc/apt/sources.list*以下のメンテナンスとkey-addを自動的に行なってくれることで、やっている事は同じです。

たしかにすっきりするんですけどね。 便利さを追求すると、初心者には優しいけれど、中級者のスキルアップには厳しいシステムになりがちなところが、ちょっと問題かな。

2011/02/04

sf.jpが提供するPersonalForgeをしばらく使ってみた感想

PersonalForgeはSourceForge.jp (sf.jp)が提供する個人向けのGitリポジトリを提供するサービスで、2011年1月13日にリリースされています。

GitHubも同時に使い始めましたが、sf.jp全般は本家のSourceForge.net (sf.net)と比べてガイドが分かりやすく、次に何をするべきか把握することができました。

sf.netを使った時には、Gitリポジトリのパスのみが表示されて、どうやってコードを配置するか直接的なガイドはありませんでした。もちろんドキュメントは探せばあります。

ガイドはGitHubもちゃんとしていますが、日本語である点も含めてsf.jpを使うメリットはあるだろうと感じました。

それに自分のパソコンの外にコードを置くのはハードディスクのクラッシュといった災害から守る意味もあります。

Gitをとりあえず個人的に使ってみたくて、最初にどう使ったらよくわからないならPersonalForgeがお勧めです。

もちろんコードにオープンソース系のライセンスを付ける事が必要ですけどね。

Subversion v.s. Git

Gitを使うと決めてしまうと、Google CodeはSubversionのリポジトリのみを提供しているため選択肢から外れてしまいます。 それがlscouchdbの開発にsf.netを選択した理由です。

GitHubやGitorisは、PersonalForgeよりもリッチな感じですけれど、それでもコードを管理するだけの機能しかないですからね。

そんなわけで、今年に入ってから本格的にPersonalForge以外にもGitを使っています。

個人的にかなり初期からSVNリポジトリを自宅に構築してきたので、経験の差はありますが、それでも個人利用であればGitの方がいろいろなストレスから開放されそうです。

SVNのブランチやタグはどちらも大きな違いはなくて、高速かつ簡単にトップディレクトリ全体を別の場所にコピーするのと同じです。 SVNでは簡単にブランチを作成する事はできますが、そんなブランチ間を移動するのは、Gitを使った今はかなり面倒に感じます。

Gitの場合はもっとドライで、ブランチ間の移動がスムーズにできるよう設計されています。

けれど、根本的にSVNとGitではリポジトリの主従関係が逆転しています。

SVNを使っていると、バックアップの有無とかに関係なく、サーバに何かあったらどうしようかと心配になります。

Gitの自分の手元のリポジトリがメインで、サーバ側がその安全なスナップショットを反映するミラーにしか過ぎないというのは、地味だけれどGitに対するポジティブな印象を持つ大きな理由でしょう。

この一点だけでもSVNを捨てようかなと真剣に検討しているほどです。

SVN固有の機能は個人開発に必要なのか

SVNではファイル名を変更してもコードの変更履歴を追跡できるところがメリットですが、Gitではそこに注力してはいません。

SVNではファイル名を変更して、さらにその上のディレクトリ名を変更したり、重複した変更を一度にコミットするような複雑な変更を一度にして競合が発生した経験があります。

gitでも似たような事をしてしまいましたが、Gitはすんなりとその有り様を受け入れました。

これはフェアな比較ではありませんが、個人利用ではSVNの機能の多くは実際は必要ないと感じました。 履歴を追跡できる事は必ずしも重要ではないわけです。

その反面、企業ユースではむしろSVNの機能が必要な場合もあり得るでしょう。

学生であればSVNを学んでおく事は必要だと思いますが、SCM初心者の選択肢としては、特に分散リポジトリが必要なくても、SVNではなくてGitを利用するメリットはあると思います。

2011/02/03

OpenOffice Calcでダブルクォートが特殊文字に変換される現象について

CSVファイルを扱っていて、OpenOfficeのデフォルトの挙動に困った事がありました。

それはデータとしてセルに"name"のように単純にダブルクォートで囲んだ場合に、特殊文字に展開されてしまうところです。

これを回避する方法は簡単にでメニューバーの「ツール」から「オートコレクトオプション…」を選択して、「ユーザ定義引用符」置換のチェックを外すだけです。

オートコレクトメニューの変更が必要な個所を赤丸で表示しています

Excelを使った場合のダブルクォートの扱い

CSVについてはrfc4180が一応、後出しですがまとまった情報という事になっています。

これによれば、以前も書きましたが、ダブルクォートは""のように2つを続けて出力することでエスケープできます。

最近はCouchDBにPUTするDocumentをCSVから生成できる方法を模索しているので、Map関数を定義してみました。

Excel2007でCouchDBのMap関数を定義してみた

language,views,all,map
javascript,,,"function(doc) {
  if(doc.doctype && doctype == ""log"")
    emit(doc.src);
}"

注目するのは、== ""log""のエスケープされたところです。

OpenOfficeを使った場合

すでに説明していますが、オートコレクトの挙動によって入力したダブルクォートは\u201c(0x28809c), \u201d(0xe2809d)に変換されてしまいます。

"_id";"language";"views";"all";"map";"views";"all";"reduce"
"_design/all";"javascript";;;"function(doc) {
  if (doc.doctype && doc.doctype == “log”) {
    emit(doc,null);
  }
}";;;"_count"

Unicodeの"Left Double Quotation Mark"や"Right ..."はかなり見分けるには厳しい違いなので、できればデフォルトの挙動としては勘弁して欲しいんですけどね。

とりあえず先頭に書いたようにオートコレクトのオプションを操作して、CSVファイルを生成しています。

2011/02/02

CouchDB: 1.0.1から1.0.2のリリースアップ手順 (xstow対応版)

家にあるalixやらworkstationやらhttp://www.yadiary.net/にあるソフトウェアのバージョン管理にはxstowを利用しています。

最初にコンパイルしたapache couchdb version 1.0.1は/usr/local/stow/couchdb-1.0.1をconfigureの'--prefix'に指定しています。

今回は始めてのバージョンアップになります。

まぁstowのマニュアルには/usr/localをprefixに指定して、make install時に変数を上書きするような方法をとるわけですが、そうすると間違って/usr/local/stow以下でコマンドを実行しても、それなりに動いてしまうのが痛いところなわけです。

さて、今回はapache-couchdb-1.0.2がリリースされてしばらく経ち、いろいろテストしてみてもそのまま移行して問題なさそうな感じなので、その作業をまとめたログです。

現行環境の確認

インストールする環境はDTIのVPSで使っているDebian squeezeです。

stowディレクトリの構造

local.iniなどはバージョンに対して不変なので、stowディレクトリには含めていません。

深さ2ぐらいまでのディレクトリ構造は次のようになっています。

$ find /usr/local/stow/couchdb-1.0.1 -maxdepth 2
/usr/local/stow/couchdb-1.0.1/etc
/usr/local/stow/couchdb-1.0.1/etc/init.d
/usr/local/stow/couchdb-1.0.1/etc/logrotate.d
/usr/local/stow/couchdb-1.0.1/share
/usr/local/stow/couchdb-1.0.1/share/man
/usr/local/stow/couchdb-1.0.1/share/couchdb
/usr/local/stow/couchdb-1.0.1/share/doc
/usr/local/stow/couchdb-1.0.1/bin
/usr/local/stow/couchdb-1.0.1/bin/couchdb
/usr/local/stow/couchdb-1.0.1/bin/couchjs
/usr/local/stow/couchdb-1.0.1/lib
/usr/local/stow/couchdb-1.0.1/lib/couchdb
テスト環境で発見した不具合

このままstow側にないディレクトリ(例: var/, etc/couchdb)を同じように削除して、バージョンアップすると、ちょっとした問題が発生しました。

stowに含めないファイルはバージョンに依存しない情報を持っている事が前提だったのですが、default.iniファイルにはいろいろとパス名を含めた情報が記述されています。

$code_cap

...
util_driver_dir = /usr/local/lib/couchdb/erlang/lib/couch-1.0.1/priv/lib
...

このため$ sudo xstow -D couchdb-1.0.1を実行してから、/usr/local/etc/couchdb/default* ファイルたちを /usr/local/stow/couchdb-1.0.1/etc/couchdb 以下に退避してから、$ sudo xstow couchdb-1.0.2を実行する手間がかかっています。

置き換え手前までの作業

今回はノーマルなcouchdb-1.0.2のパッケージに加えて、自作のパッチを適用しています。

準備したファイルは次の通りです。

gropu_numrows関係のファイルは差分ではないので、直接置き換えます。 差分はGitHub上で確認できます。

コンパイルとインストール
$ tar xvzf apache-couchdb-1.0.2.tar.gz
$ mkdir apache-couchdb-1.0.2.extras
$ cd apache-couchdb-1.0.2.extras
$ curl -o couch_db.hrl https://github.com/YasuhiroABE/CouchDB-Group_NumRows/raw/couchdb-1.0.2/couch_db.hrl
$ curl -o couch_httpd_view.erl https://github.com/YasuhiroABE/CouchDB-Group_NumRows/raw/couchdb-1.0.2/couch_httpd_view.erl
$ cp couch_db.hrl couch_httpd_view.erl ../apache-couchdb-1.0.2/src/couchdb/
$ cd ../apache-couchdb-1.0.2
$ ./configure --prefix=/usr/local/stow/couchdb-1.0.2
$ make
$ sudo mkdir /usr/local/stow/couchdb-1.0.2
$ sudo chown $(id -un) /usr/local/stow/couchdb-1.0.2
$ make install
$ sudo chown -R root:couchdb /usr/local/stow/couchdb-1.0.2
stowで管理しない不要なファイル・ディレクトリの削除

インストールまでは、これで終りで次にvarディレクトリなど/usr/local/以下に直接配置するファイルやディレクトリを削除していきます。

新規に導入する場合には、削除ではなくて、対応する/usr/localの場所にmvする事になります。

$ sudo rm -rf /usr/local/stow/couchdb-1.0.2/var
$ sudo rm -rf /usr/local/stow/couchdb-1.0.2/etc/couchdb/local.*
$ sudo rm -rf /usr/local/stow/couchdb-1.0.2/etc/default
埋め込まれた"stow/couchdb-1.0.2/"パスの削除

スクリプトファイルなどの中からstow/を含むパスを通じて参照している部分を書き換えて/usr/local/stowを参照するようにします。

候補は次のコマンドラインで表示されますが、lib/couchdb/以下のファイルは設定ファイルで指定するので作業を行ないません。

ファイルを開いて書き換えますが、viを使った場合のコマンドラインは次のようになります → :%s!stow/couchdb-1.0.2/!!

 $ find /usr/local/stow/couchdb-1.0.2/ -type f | while read file ; do grep stow/ $file > /dev/null 2>&1 && echo $file ;  done
/usr/local/stow/couchdb-1.0.2/etc/init.d/couchdb
/usr/local/stow/couchdb-1.0.2/etc/logrotate.d/couchdb
/usr/local/stow/couchdb-1.0.2/etc/couchdb/default.ini
/usr/local/stow/couchdb-1.0.2/lib/couchdb/erlang/lib/couch-1.0.2/ebin/couch.app
/usr/local/stow/couchdb-1.0.2/lib/couchdb/erlang/lib/couch-1.0.2/priv/lib/couch_icu_driver.la
/usr/local/stow/couchdb-1.0.2/bin/couchjs
/usr/local/stow/couchdb-1.0.2/bin/couchdb

これで準備作業は全て完了しました。

couchdb-1.0.1からのバージョンアップ

最初に説明したように、通常であればcouchdbで停止してxstowコマンドで切り替えるだけなのですが、今回はstowで管理するファイルにdefault.iniとdefault.dを追加したのでxstowでcouchdb-1.0.1のリンクを削除した後で、このファイルを手動で移動する必要があります。

たぶんコマンドラインは次のようになるはずです。

$ cd /usr/local/stow
$ sudo /etc/init.d/couchdb stop
$ ps auxwww|grep couchdb          ## ← eralangプロセス(beam)の停止を確認
$ sudo xstow -D couchdb-1.0.1
$ sudo mkdir couchdb-1.0.1/etc/couchdb
$ sudo mv ../etc/couchdb/default.* couchdb-1.0.1/etc/couchdb
$ sudo xstow couchdb-1.0.2
$ sudo /etc/init.d/couchdb start

とりあえずこの手順通りで無事にhttp://www.yadiary.net/で動いているCouchDBを1.0.1から.1.0.2に移行する事ができました。