2012-01-29
タブでapacheのログを区切る件について
apacheのログをタブで区切ると良い、という話を、nginx + unicornでrails3.1が動作する環境を作る - A Peak Never Ending !のnginxの設定ファイルのログ出力周りを見ていて思い出した。
/etc/nginx/nginx.conf:
(中略)
log_format main '$msec\t'
'$status\t'
'$request_time\t'
'$remote_addr\t'
'$upstream_addr\t'
'$upstream_response_time\t'
'$request\t'
'$http_referer\t'
'$http_user_agent';
その話の記事は以下になります。
404 Blog Not Found:tips - ApacheのLogフォーマットの方を変えて高速化
Benchmark: running rx_naive, rx_optim, tsv for at least 3 CPU seconds... rx_naive: 3 wallclock secs ( 3.15 usr + 0.00 sys = 3.15 CPU) @ 2417.71/s (n=7612) rx_optim: 3 wallclock secs ( 3.78 usr + 0.00 sys = 3.78 CPU) @ 78526.94/s (n=296930) tsv: 4 wallclock secs ( 3.02 usr + 0.00 sys = 3.02 CPU) @ 646447.96/s (n=1954495) Rate rx_naive rx_optim tsv rx_naive 2418/s -- -97% -100% rx_optim 78527/s 3148% -- -88% tsv 646448/s 26638% 723% --
タブ区切りをparseすると、実に10倍程度、早い。
#!/usr/local/bin/perl use strict; use warnings; use Benchmark qw/timethese cmpthese/; my $logline = q{localhost.local - - [04/Oct/2007:12:34:56 +0900] "GET /apache_pb.gif HTTP/1.1" 200 2326 "http://www.dan.co.jp/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.7) Gecko/20070914 Firefox/2.0.0.7"}; my (@logline) = ( $logline =~ m!^(.*) (.*) (.*) \[(.*)\] "(.*)" (.*) (.*) "(.*)" "(.*)"! ); my $tsv = join "\t", @logline; cmpthese( timethese( 0, { rx_naive => sub { my (@l) = ( $logline =~ m!^(.*) (.*) (.*) \[(.*)\] "(.*)" (.*) (.*) "(.*)" "(.*)"! ); }, rx_optim => sub { my (@l) = ( $logline =~ m!^([^\s]*) [^\s]* [^\s]* \[([^]]*)\] "([^"]*)" ([^\s]*) [^\s]* "([^"]*)" "([^"]*)"! ); }, tsv => sub { my (@l) = split /\t/, $logline }, } ) );
かなり前にapacheのログを自分で解析するプログラムを書いたときに、とてもお世話になったコードです。
で、実はこのコードには問題があって、
31c31
< tsv => sub { my (@l) = split /\t/, $logline },
---
> tsv => sub { my (@l) = split /\t/, $tsv },
というpatchを当てないと正確な値が出ないのだと思うのです。perlを書いたことがないので正しいのかどうか分からんけれども。
結果はこうなります。
$ ./ben2.pl Benchmark: running rx_naive, rx_optim, tsv for at least 3 CPU seconds... rx_naive: 3 wallclock secs ( 3.13 usr + 0.00 sys = 3.13 CPU) @ 4771.25/s (n=14934) rx_optim: 3 wallclock secs ( 3.12 usr + 0.00 sys = 3.12 CPU) @ 168081.41/s (n=524414) tsv: 2 wallclock secs ( 3.32 usr + 0.00 sys = 3.32 CPU) @ 278219.28/s (n=923688) Rate rx_naive rx_optim tsv rx_naive 4771/s -- -97% -98% rx_optim 168081/s 3423% -- -40% tsv 278219/s 5731% 66% --
それでも66%増なので、タブ区切りの方が良いのです。前述のnginxの設定ファイルのように、タブ区切りを採用しています。
この件について、twitterでもはてブでも指摘している人は居なかったので、
404 Blog Not Found:コードについて書く方がコードを書くより読まれる現実
読者のほとんどは、コードを読みたくないのだ、ということ。 ざっくり、記事中にコードが一行あるだけで読者は半減する。
ということは、そのとおりなんだろうなぁ、と思いました。
おわり。
2012-01-28
pjaxというもの
pjaxというものの話題を目にしたのは昨年だった。
javascriptを用いて動的なページ変化を作ろうと流行ったのはajaxだった。そこにpushStateを用いた履歴管理を含むpjax(pushState + Ajax)なるものが登場した。
Ajaxによるページ遷移はそうではないものに比べて、jsファイルの再ロードや冗長なレンダリングにかかる時間を削減することができる。加えてサーバ側では冗長なHTMLを吐くことを削減できるため、処理時間が短く済む。
通常、Ajaxによる画面遷移では履歴が残らないという問題を持つ。それをpushState関数を使うことで追加できるという。それだけでなく、Ajaxを利用したページ遷移に関連するいくつかの問題も解決している。
Ajaxを用いたページ遷移を行う場合、専用のjsもしくはhtmlの断片を送信することになる。このURLにアクセスした場合、意味を成さない断片をユーザーは手にする。履歴管理をしていても無駄である。この問題の解決にpjaxではHTTPヘッダを用いる。pjaxを利用した画面遷移のURLリクエストに関してはHTTPヘッダにPJAX(HTTP_X_PJAX)を追加する。WebアプリケーションはPJAXヘッダの有無を見ることで、それがpjaxリクエストかどうかを判断し、PJAXリクエストであれば断片となるHTMLを、そうでなければフルセットのHTMLを返信する。
pjaxの最たる例はgithubのレポジトリブラウザとされている。レポジトリブラウザにて何回かの遷移を行った後、アドレスバーに表示されるURLをコピー&ペーストしても、断片ではないフルセットのHTMLが表示される。またレポジトリブラウザの遷移にはアニメーションが用いられるため、フォルダの階層を移動していることが分かりやすい。
Railsにおけるpjax
pjax, Rails, ajax, javascript
このPjaxを実現するためのRailsプラグインとしてpjax_railsが作成されている。
フレームワークにとってこのプラグインは、PJAXヘッダの確認、jquery-pjaxのロード周りの整備などを行う。PJAXヘッダは、HTTPヘッダにおけるPJAXヘッダの有無を確認して、一部のHTMLを返すのか、フルセットのHTMLを返すのか、の判断を行う。
pjax特有のものとして、ページ読み込み時のjavascript読み込み(例えばhead内のscriptタグによる)が行われないため、初期化すべき箇所は自分でハンドリングしならない。
また、このプラグインでは、フルセット部分のレイアウトが行われなくなるため、ページに合わせてtitleタグを変更することも自分で行う必要がある。
最近ではRailsよりもWebサーバ側に近いRack側でpjaxに対応するプラグインが登場している。
https://github.com/eval/rack-pjax/
このプラグインは既存のpjax_railsにおいて、タイトルの変更が難しい点を解決している。短いコードでpjaxを実装している。
https://github.com/eval/rack-pjax/blob/master/lib/rack/pjax.rb
HTTPリクエストがあればRailsがフルセットのHTMLをRackに応答する。
PJAXヘッダがHTTPリクエストに存在していたかを確認し、存在していればpjaxとして扱う。その応答をHTMLパーサ(Hpricot)にてパースし、data-pjax-container属性のあるタグとtitleタグを抜き出して、ブラウザに応答する。抜き出して送信するため、送信サイズは小さくなる。PJAXヘッダが存在していなければ、そのまま返す。
Hpricotによるパーサの影響のためか、aタグの下にdivタグなどを置くと、変なことになる(そもそも置いてはいけない)。
サーバ側の処理を少なくするという利点を殺しているが、レンダリングはpjax_railsよりも意図したとおりに行われるので、手間はこちらの方が少ない。
斜め上、jQuery Mobile
また、別のものとして、jQuery Mobileが存在する。
jQuery MobileもAjaxによるページ遷移を行うが、これはHTMLの抜き出しを必要とせずに、常にフルセットのHTMLを読み込む。フルセットのHTMLの中から、jQuery Mobileが必要とする部分をクライアントのブラウザ上のjsにて判断し、抜き出して表示する。
jQuery Mobileのための独特な属性をHTML上に書く必要がある。
フルセットのHTMLを受信するため、サーバ側の送信データサイズは小さくならないが、画面遷移のための対応をサーバ上アプリケーションでする必要がない、という利点がある。ajaxによるページ遷移の利点のうち、js再ロードとレンダリングが入らないので、これもそれなりに使える。
が、ページ遷移時のscript問題はそのままなので、例えばgoogle analysisなどは独自に対応する必要がある。
ちなみにjQuery Mobileはαの時代に触ったきりなので、リリース後のことはしらない。
rack-pjaxとjQuery Mobile
rack-pjaxはjQuery Mobile的なフルセットな生成を必要としながらも、サーバ上でパーサーを働かせて抜き出してサイズを小さくしてくれる的なもの、に見える。つまり、サーバ側でパースするのか、ブラウザ側でパースするのか、の違い、ということだ。
まとめ
で、いろいろ、あれだけれども、rack-pjaxはそれなりに気に入っているのです。
| プラグイン | サーバ処理 | データ送信量 | ブラウザ側処理 | 手間 |
| pjax_rails | -layout部分view | 小 | 小 | 大(layoutしない部分の対応) |
| rack-pjax | +HTMLパース | 小 | 小 | 小(pjax独特の初期化) |
| jQuery Mobile | 普通 | 普通 | 小 | 中(独自タグ習得) |
実際にこの手のサイトを作ったことがないので、どうとは言えないのだけれども、pjaxやり始めならrack-pjaxあたりで良いと思う。ソースを見ても分かるとおり、とても単純なので。慣れてきたらpjax_railsあたりを使うと良いかと。
jQuery Mobileは昔はもっさりしている気がしたのだが、今は知らない。
あれ
という疑問も出てくるので測定は大事ですね。。
pjaxプラグインの問題点
例えば、この部分をpjaxで変える、と決めたら、そのdivタグにdata-pjax-containerという属性を加える。
<div data-pjax-container></div>
そこに決めうちされているので、例えば、左右2ペインのページで左右それぞれをpjaxで更新したい場合は独自に作りこむ必要がある。後は変更したい箇所が入れ子になっている場合も。
なので複雑なことをしようとすると、フレームワークのlayoutの部分と密接に関わるプラグインを作っていかないと、面倒なことになりそうな予感がする。サイトの1部分のみを変更していく、という話ならば、そこまで難しくはない。
2011-11-18
Vimのプラグイン管理Vundleにてrails.vimを入れるとautoload周りでエラーが出る件について
vimのプラグイン管理
vimのプラグイン管理に関して、以前はvim-pathogenを使ってbundleディレクトリ以下にプラグインファイルをまとめる管理をしていた。標準のvimのやり方だと、プラグインのファイルが混ざってしまい、特定のプラグインを削除したり、更新したりをするためにファイルを1つ1つ確認せねばならず面倒になるからだ。
最近はvim-pathogenを一歩進めたvundleというプラグインを使っている。これは設定ファイルを書いておけば、vim上からプラグインを自動でダウンロード、更新してbundleディレクトリ以下で管理してくれる。vundleはRailsのgem管理に使われているBundleを参考にしているらしい。
ということでvundleを使っている。
rails.vimでautoload周りのエラーが出る
ところが最近、rails.vimの読み込みで失敗してしまう。調べてみたところ、以下の記事が見つかった。
" Fetch 'rails.vim' plugin from github.
としたところ、ちゃんと使えるようになりました。
http://d.hatena.ne.jp/Umeyashiki/20111001/1317488217
上記のとおり、rails.vimからtpope/vim-railsに変更すると上手く動作する。
これはどういうことなのか。そもそもvundleの動作原理を知らなかったのでソースを参照することにした。
vundleの仕組みを調べる
https://github.com/gmarik/vundle/blob/master/autoload/vundle/config.vim
これを見ると、parse_nameで何処にレポジトリを見に行くのか決定している。
例えばレポジトリの設定を
のように書いていくのだが、この書き方によって変わる。
- https://github.com/以下を見に行く
- そのままアクセスする
- https://github.com/vim-scripts/以下を見に行く
- 上記以外の場合
よって、
Bundle 'tpope/vim-fugitive'
のような指定があった場合、tpope/vim-fugitiveは[文字列]/[文字列]なので、https://github.com/tpope/vim-fugitive.gitを、git://git.wincent.com/command-t.gitはそのまま、rails.vimはhttps://github.com/vim-scripts/rails.vimを見に行く。
https://github.com/vim-scripts/rails.vim のレポジトリの中を見ると、autoloadディレクトリはなく、2011/8/31の更新のままであり、かつミラーであると記されている。本家のhttps://github.com/tpope/vim-railsは5日前に変更されたばかりのファイルがあり、前述のミラーは多少古いようだ。
結論
rails.vimのように、単純に書いただけだと割と古い https://github.com/vim-scripts/ のスクリプトを掴んでしまうので、多少面倒でもgithubでプラグインの執筆者を調べてtpope/vim-railsのように書いたほうが最新のプラグインを使えるので楽しいかと思う。
2011-07-17
Ruby1.9のCGI.unescapeHTMLにて実体参照の戻しを行う場合には変換元の文字列のエンコーディングを考慮する必要がある
Ruby1.9で遊び始めた。WebのAPI叩いていたら、実体参照が含まれる文字列をunescapeHTMLしようとしても上手くいかない。例えば、""のような形式の実体参照。
Ruby1.8の場合には問題とならなかったケースだった。cgi.rbのソースコードを読んで、ようやく原因が分かった。
cgi/util.rb@1.9.2p180に含まれる、一節を抜粋する。
def CGI::unescapeHTML(string) enc = string.encoding #(中略) string.gsub(/&(amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/) do match = $1.dup case match #(中略) when /\A#x([0-9a-f]+)\z/i n = $1.hex if enc == Encoding::UTF_8 or enc == Encoding::ISO_8859_1 && n < 256 or asciicompat && n < 128 n.chr(enc) else "&#x#{$1};" end else
上記のwhen節は正規表現で"�"形式を抜き出したあとの処理になる。encとは、引数stringのencodingを示し、この入ってくるstringのエンコーディングがUTF-8か、それ以外で処理が異なる。
Ruby1.9ではそれぞれの文字列にそれぞれのエンコーディングが関連付けられる。よって引数としてのstringにASCIIがエンコディングとなっている文字列が入っているのではないか、と考えられる。
#!/usr/bin/env ruby require 'cgi' print "RUBY_VERSION ", RUBY_VERSION,"\n\n" word = "通常" print "encoding: ", word.encoding, "\n" print "word: ", CGI.unescapeHTML(word), "\n\n" word_utf8 = word.encode("UTF-8") print "encoding: ", word_utf8.encoding, "\n" print "word: ", CGI.unescapeHTML(word_utf8), "\n\n"
上記に検証コードを示す。下記に実行結果を示す。
$ ruby unescape.rb RUBY_VERSION 1.9.2 encoding: US-ASCII word: 通常 encoding: UTF-8 word: 通常
推測のとおり、US-ASCIIを入れてしまっていたようだ。文字コードを利用して整数から文字に変換するためには、当然、変換先のエンコーディングが必要になる。US-ASCIIのようなエンコーディング指定では、無理に変換しようとせずに、無視される。
Ruby1.9ではプログラムの冒頭にてリテラル文字列のエンコーディングに何を使うかの指定が行える。そのため通常は問題にならなそうだ。
# -*- encoding: utf-8 -*-
今回のケースでは、WebのAPIを叩く過程のどこかでASCIIエンコーディングの文字列が返ってきたようだ。どこで入っていたのかについては確認をしていない。
あまりにもアホなミスなのか、ネット上にunescapeHTMLについての事例の情報がなかった。恥ずかしいが、書いておこうと思う。
2010-07-02
vimでruby,railsのインストールをUbuntu 10.4で
Ubuntu 10.4が出たのでvimでrailsのコードを編集しやすい環境を構築中。オムニ補完が欲しいので、rubyライブラリにリンクしたvimをインストールする。
sudo aptitude install vim-nox
当初は vim-basic あたりで十分かと思っていた。が、
コマンドでrubyがリンクされているかどうか確認してみたところ、 +ruby ではなく -ruby なんぞが出てきた。面倒なので vim の再コンパイルをしようとしたが、尋常ではない依存関係のパッケージの多さに諦めた。debian/rulesを読むと、vim-gnome や vim-nox なんかは ruby ライブラリを有効にしてある設定だったので、それを入れることにした。
が、次は rubygems がない。
sudo aptitude install rubygems
ruby-enterprise を入れていたので、Ubuntu の ruby は入れていなかった。混在するのは得策ではないと考えたからだ。諦めた。素直に rubygems を入れよう。
次は rails が入っていないと...
sudo /usr/bin/gem install rails
うん。もう既に vim-rails を入れない理由はない。素直に vim-rails を入れよう。
sudo aptitude install vim-rails vim-ruby-install.rb vim-rails-setup