Emacsからオフラインで使えるリファレンスブラウザDashを呼び出す

追記 2/17

現在のmajor-modeに対応して、検索対象のdocsetが絞りこまれるようにしました。以下のようにカスタマイズすることもできます。

(add-to-list 'dash-at-point-mode-alist '(perl-mode . "perl"))

追記ここまで

OSXで開発している時に各言語やライブラリのリファレンスをオフラインで検索したい、とTweetしたところ、

id:naoyaさんにお勧めしてもらったDashを試してみました。各言語、ライブラリのリファレンスが簡単にダウンロードでき、それをオフラインで高速に検索できて、これは良いですね。

Dashのスクリーンショットはこんな感じです。

f:id:stanaka:20130218134501p:plain

Windows時代のころは、chmという複数のhtmlファイルを一つにまとめた形式があって、各種リファレンスをまとめるのに都合が良いものでした。ただ、OSXでは、良さげなクライアントが見つからず、あきらめて、毎回ググってましたが、相当ストレスでしたからね。それが改善されて、捗ります。

この調子で便利に使っていると、いつも使っているエディタ、Emacsと連携させたくなります。幸いDashでは、

open dash://{query}

とするとコマンドラインから検索できる、とのことです。というわけで、久し振りにEmacs Lispを書いてみました。dash-at-point.elというものです。カーソル下のワードをDashで検索できます。これで、コードを書き/読みながら、リファレンスを引きたくなった時にさくっと引けます。

ソースは https://github.com/stanaka/dash-at-point にあります。以下を".emacs"に書くと使えます。

(add-to-list 'load-path "/path/to/dash-at-point")
(autoload 'dash-at-point "dash-at-point"
          "Search the word at point with Dash." t nil)
(global-set-key "\C-cd" 'dash-at-point)

最近は、EmacsもPackage systemに乗るのが流行りのようなので、MELPAにでもpull requestでも送ってみるのがよいですかね。

ログをLTSVやJSONで保存した場合のサイズ比較

追記(2/17)

変換スクリプトを見せてほしい、という要望があったので、 https://gist.github.com/stanaka/4967403 に上げておきました。ltsvを読み込んでオプションで指定したフォーマット(デフォルト JSON)に変更します。

追記ここまで

LTSVの盛り上りも収束してきていますが、サイズに関する懸念があがっていたので、確認してみました。

手近にあったアクセスログ 186万件ほどを対象に、

  • ssv .. Combined Log Formatの拡張で、ラベルなし (レスポンス時間とか10個ぐらいのフィールドを拡張しています)
  • json .. ラベルあり
  • ltsv .. ラベルあり

の3パターンで試してみました。

まずは行数を確認しておきます。

% wc -l access_log.json
1861706 access_log.json

未圧縮だと、

access_log.ssv    818,729,505 (781M)
access_log.json 1,282,452,709 (1.2G)
access_log.ltsv 1,111,017,347 (1.1G)

でした。1G前後でそれなりのサイズのログですね。ssvとltsvの差は300M程度。jsonにするとさらに150Mぐらいでかくなってます。

最初にgz圧縮してみると、

access_log.ssv.gz  136,835,984 (131M)
access_log.json.gz 152,432,366 (146M)
access_log.ltsv.gz 146,072,000 (140M)

131M〜146Mでだいぶ差が縮まりました。1割程度の差を問題視するかどうかはポリシー次第ですが、最近では1割程度は誤差なんじゃないかと思います。

さらにbz2圧縮してみると、

access_log.ssv.bz2   93,621,212 (90M)
access_log.json.bz2 100,358,979 (96M)
access_log.ltsv.bz2  98,269,135 (94M)

と差は5%とさらに縮まりました。スペースを気にしつつ、bz2の圧縮時間に耐えられるなら、これもアリだと思います。

ついでなので、7z圧縮してみました。manの例にも載ってた推奨(?)のオプションでやってみました。

7z a -t7z -m0=lzma -mx=9 -mfb=64 -md=32m -ms=on access_log.ltsv.7z access_log.ltsv

結果は、

access_log.ssv.7z  71,518,534 (69M)
access_log.json.7z 79,827,214 (77M)
access_log.ltsv.7z 77,032,835 (74M)

という感じで、さらに小さくなりました。差はbzip2に比べると、むしろ広がってますね。

まとめ

伝統的なCombine Log Formatの拡張、JSON, LTSVで比べてみましたが、JSON, LTSVともに圧縮すれば1割程度の増加で済むので、JSONにせよLTSVにせよ、気にせずにラベルを付けて、使い勝手重視で行くのがよいんじゃないでしょうか。

Labeled Tab Separated Values (LTSV) ノススメ

追記(2/8 11:30)

追記ここまで

Labeled Tab Separated Values (LTSV) というのは、はてなで使っているログフォーマットのことで、広く使われているTSV(Tab Separated Value)フォーマットにラベルを付けて扱い易くしたものです。はてなでは、もう3年以上、このフォーマットでログを残していて、one-linerからfluentd、Apache Hiveまで幅広く便利に使えています。

ログフォーマットに期待されることは、

  • フォーマットが統一されている → 共通のツールで集計し易い
  • 新しいフィールドの追加が容易 → サービス固有のデータをログに残したいことはよくある。かつ、複数サービスに渡って、別々のエンジニアが触っても混乱しない
  • one linerで操作しやすい → grepした後でsortなり、uniqなりで集計するのはよくあること

の3点だと考えています。

これまでにも、ログフォーマットを扱い易くする取り組みとして、SyslogでもRFC5425で構造化の仕様が入ったり*1していますが、いまいち普及しているとは言えません。Apache標準のCombined Log Formatを独自拡張して頑張る、というのがよくある光景なんじゃないかと思います。ただ、サービスごとの要求に応じて、どんどん独自拡張していると個別にParserの定義を書かないといけなくなったり、fluentdで扱う際にも頑張ってサービスごとの定義を正規表現で書かないといけない羽目になって、どんどん辛くなります。

Labeled Tab Separated Values (LTSV) とは

そこではてなでは、3年ほど前*2からLabeled Tab Separated Values (LTSV) によるログフォーマットを全面的に採用しています。

Labeled Tab Separated Valueは、

time:[28/Feb/2013:12:00:00 +0900]	host:192.168.0.1	req:GET /list HTTP/1.1	status:200	size:5316	referer:- ua:Mozilla/5.0	taken:9789	isrobot:1	dos:-	harddos:-	cache:-

というもので、各フィールドがタブで区切られ、フィールドの頭にラベルを付けています。

ApacheのLogFormat定義は

LogFormat "host:%h\ttime:%t\treq:%r\tstatus:%>s\tsize:%b\treferer:%{Referer}i\tua:%{User-Agent}i\ttaken:%D\tisbot:%{Isbot}e\tdos:%{SuspectDoS}e\tharddos:%{SuspectHardDoS}ecache:%{X-Cache}o" ltsv

となり、nginxでは

log_format tsv "time:$time_local\thost:$remote_addr\treq:$request\tstatus:$status\tsize:$body_bytes_sent\treferer:$http_referer\tua:$http_user_agent\treqtime:$request_time\tcache:$upstream_http_x_cache";

となります。

このLabeled Tab Separeted Valueフォーマットのメリットは、まずログを定義する時に各フィールドの順序を意識しなくてよく、各プロダクトで容易に独自拡張できる、ということにあります。上の例では、X-Cacheヘッダやmod_dosdetectorの判定結果を残したりしています。はてなでの運用では、ラベルをWikiで管理しており、新しいラベルを作る際には記録してもらうようにしています。

Labeled Tab Separated Valueを扱う

LTSVはTSVの拡張ですので、cutコマンドで簡単に切り分けられます。例えば、ステータスコードごとに数えるとすると以下のようになります。

% tail -n 100 /var/log/httpd/access_log.ltsv | cut -f 5 | sort | uniq -c
     97 status:200
      2 status:301
      1 status:404

ただ、これだけだと、ラベルが出て、ちょっと分かり易いぐらいですかね。

では、次にfluentd pluginを見てみます。コードは、https://github.com/stanaka/fluent-plugin-tail-labeled-tsvにあります(近々Gem化します)。fluentdでの定義は、

<source>
  type tail_labeled_tsv
  path /var/log/nginx/access_log.ltsv
  tag access_log
  pos_file /tmp/fluent.log.pos
</source>

のようになります。in_tail pluginのように非標準のフォーマットでも頑張って定義を正規表現で書く必要がありません。Webサーバー側での定義を変更してもfluentd側の設定を触らずにすむのも便利です。

さらにApache HadoopのHive用のSerDe(シリアライザ・デシリアライザ)も用意しています。コードは、 https://github.com/hatena/KeyValuePairsDeserializer にあります。

これにより、Amazon EMR上のHiveで簡単にテーブルを定義することができます。

ADD JAR s3://sample/_lib/hadoop/hive/serde/hatena-serde.jar;

CREATE EXTERNAL TABLE logs (
  time string, host string,
  req string, status string, size string,
  referer string, ua string
) 
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'jp.ne.hatena.hadoop.hive.serde.KeyValuePairsDeserializer';

ALTER TABLE sample ADD PARTITION (dt='2013-01-01') LOCATION 's3://sample/sample_log.ltsv.gz';

その後、Hiveにより、MapReduceでログ解析を行うことができます。例えば、

select count(distinct referer) from logs

のようなHiveQLを流すことで、1年分のログでも、がりっと処理することができます。

まとめ

はてなでは、Labeled Tab Separated Valueによるログフォーマットで統一していて、one-linerからfluentd、Amazon EMR上のHiveまで使えて便利!という話でした。

*1:ところで、Parser書いている人もいますね。 http://blog.kentarok.org/entry/20101217/1292606627

*2:確認すると2009年10月頃からでした

Kindle Paperwhite向けに自炊pdfを最適化する

id:halfrackKindle Paperwhiteのタワーを作っていたので、Kindle Paperwhite向け自炊pdfの最適化をしてみました。
f:id:halfrack:20121011205048j:image:w200

Kindle Paperwhite向けの自炊pdf最適化は、

  • 余白を適切に削除すること
  • 画像を最適なサイズである横658ドット x 縦905ドットにすること

の2つが大事です。特に後者が重要で、デバイスごとに最適なサイズにすることでpixel by pixelで表示することができます。ちなみに、これまでのKindleでは横560ドット x 縦735ドットが最適でした*1

調査には1ピクセルごとに線を入れたpdfを作成して行いました。このpdfでは、ページごとに画像サイズを1ピクセルずつ変えていて、最適な解像度ではない場合、綺麗に一様な模様にならず、すぐに分かります。これでちょっとずつ探りながら最適なサイズを探すわけです。地道ですね。実際に試したい場合は、使ったpdfをkindle_paperwhite_resolution.pdfに置きましたのでどうぞ。作成スクリプトは https://gist.github.com/3892964 にあります。

Kindle Paperwhite向けに最適化されたのがどれぐらい綺麗かというと、
f:id:stanaka:20121016002842j:image:w200 f:id:stanaka:20121016002841j:image:w200
ぐらい違います。いずれも元pdfは同じで、左がKindle4(最適化済)、右がKindle Paperwhite(最適化済)です。「聖職」に注目すると、文字の細かい線がKindle Paperwhiteのほうがはっきりしていますね。

次は最適化するためのツールが欲しい、ということで、Kindle向けのpdf最適化を行うツールの中から Kindlizer( https://github.com/tdtds/kindlizer )からforkして、https://github.com/stanaka/kindlizer で手を入れてKindle Paperwhite向けにできるようにしました。ただただしさん、ありがとうございます!

Kindlizerの解説や詳しい使い方は、 http://d.hatena.ne.jp/kamosawa/20111116 にあります。
Macでは依存ツールはbrewでざくざく入ります。

brew install sam2p
brew install imagemagick
brew install pdftk
brew install poppler --with-glib

popplerはオプション無しではビルドに失敗した(OSX 10.7.4, XCode 4.5.1)のですが、"--with-glib"をつけたら成功しました。rakeは既に入っていますよね。当然です。

fork版はオリジナルからは以下の変更を加えています。

  • Rakefileをハードコードされていた元pdf(SRC)やマージン(TOP, BOTTOM, LEFT, RIGHT)、サイズ(SIZE)、階調(LEVEL)を引数で指定可能にした
  • 元pdfのファイル名が空白などを含んでいても処理可能にした
  • sample.pdf → sample.out.pdf と変更されていたファイル名のout部分を変更可能にした(POSTFIX)
  • i文庫向けに"[著者名] タイトル.pdf"というルールのファイル名を"タイトル.out.pdf"にしつつ、AUTHORのメタデータを書き換えるようなオプション(UPDATE_METADATA)を新設した

これを使って、Kindle Paperwhite向けの最適化を実行するには、元ファイルをsample.pdfとして用意して、

rake SIZE="658x905"

と一発でいけます。

複数ファイルを対象としたい( & 美白化したい & マージンの調整をしたい & 著者名をメタデータに入れたい)時も

for i in ~/book/*;do rake SRC=$i LEVEL='0%,93%,0.7' TOP=100 UPDATE_METADATA=1 POSTFIX=kindle; rake clean SRC=$i; done

ワンライナーで一発でいけます。

では、快適なKindle Paperwhiteライフを!

Kindle Paperwhite

Kindle Paperwhite


Kindle Paperwhite 3G

Kindle Paperwhite 3G

追記(10/16 9:52)

コメントでjpegをzipしたものなら、リサイズもまともですよ!ということなので、Kindle4で試してみましたが、コントラストの調整ができなくて、ちょっと厳しそう。ページ数が出ないのもナビゲーション的に厳しいですね。あと、Paperwhiteではjpegのzipファイルは認識してくれなかったので、サポート外になったのかもしれません。(同じzipでKindle4では見れました)
f:id:stanaka:20121016095141j:image:w200

追記 2(10/16 10:17)

Kindle Paperwhiteでのスクリーンショットの撮り方は、

All you have to do to take a screenshot is tap the upper left corner and lower right corner at the same time. Or you can tap the upper right corner and lower left corner at the same time.

http://blog.the-ebook-reader.com/2012/10/08/kindle-paperwhite-tips-how-to-take-screenshots-and-remove-recommended-ebooks/

ということで、右上と左下を同時にタップ、もしくは左上と右下を同時にタップで、ルートディレクトリにPNGファイルが生成されます。

*1:参考: http://non-non-chan.blogspot.jp/2010/10/kindle-3-2.html