qunit-tapとproveを使ってJSの単体テストのCIをする方法
「phantomjs + qunit-tapでCLIで単体テスト」をするという素晴らしい方法をt-wadaさんが書かれてました。これを少し応用するだけでJavaScriptの単体テストのCIすることができます。
http://www.slideshare.net/t_wada/wandering-about-javascript-testing
FTレベルの自動化はSeleniumでいいかなと思ってたんですが、単体テストレベルのCIの自動化には今まであまり良いソリューションがありませんでした。
ブラウザのシミュレータ系はいつもシミュレートする側のバグなのかという制約を意識しながら進めることになり、今まで何回か導入しようとして失敗していました。
しかし、qunit-tap + phantomjs + (prove + TAP::Harness::JUnit) + Jenkins を使えば、この問題が解決できます。phantomjsを使う方法であれば実際のブラウザに限りになく近いわけで、これはとても良さそうです。
JavaScriptのUTのCIまでできれば、チームでも使えるようにになりそうです。そして、これは以前PerlのプロジェクトでJenkins(Hudson)を使う方法を説明しましたが、その方法で実現できます。
http://dann.g.hatena.ne.jp/dann/20100404/p1
qunit-tapというt-wadaさんのプロダクトは、TAPでの出力に対応しています。ですから、proveとTAP::Harness::JUnitを組み合わせれば、JUnit形式の結果の出力ファイルを生成することができます。
要するに、以下のようにJenkins側でproveを実行して、Jenkinsに出力されたJUnit形式のXMLファイルを食わせれば、CIを実現することができます。
prove --harness TAP::Harness::JUnit ./phantomjs_test.sh
phantomjs_test.shのサンプルについては、t-wadaさんのレポジトリに例があるので、少し書き換えて使えばいいですね。
https://github.com/twada/qunit-tap/blob/master/sample/js/phantomjs_test.sh
これでFTレベルだけでなく、javascriptのUTレベルでもCIができるようになり、チーム開発でも使えるようになりますね。t-wada++
トランザクション数の多いDBサーバーのディスク構成について
IOPSが高い値を維持でき、ある程度コストを抑えたDBサーバーのディスク構成を考えてみる。RAIDカードを全てのスレーブに置くというのは価格がかかりすぎること、SSDが安くかえるようになったことを考えると、以下のような構成が、そこそこの価格でIOPSを稼ぐための構成だと考える。
DBサーバーの構成
- マスターDBのサーバー
- スレーブDBサーバー
マスターについては、(RAID+BBWC) + SSD、(RAID+BBWC)+HDDで、ライトのIOPSと運用体制によっていずれかを選ぶ。また、トランザクション数が極端に大きくなく、定常的にトランザクション数が多いという状態でなければ、RAID+BBWCとHDDでの運用でよい。
SSDの運用について
SSDを使う場合には、HDDと違う運用の検討が必要になってくるので、その点注意が必要である。注意点だけでなく、それを考慮した運用が行える体制があるかが、導入上大きなポイントになる。
FusionIO/tachIOn の使用について
- FusionIO, tachIOnが数万のIOPSが出ることを考えると、書き込みするデータ量が多くなければ、オーダーとして書き込み回数が10倍以上多くてもよいわけで、DBサーバーを最大1/10程度にできる可能性はあり、それを考えると十分に検討の余地はありそう。
- トランザクション数が多く、導入しない場合に、台数が増えて、運用コストが初期のイニシャルコストを超える場合には、導入を検討する価値がある。ただし、本当の大規模サービスでなければ、それをペイできる環境ではないとは思う。
IOPS視点のディスクのプラニングについて
以下の2点を行い、IOPSの増加に対してどのようなプランをたてるかを検討することが重要。I/Oサブシステムの性能を意識しておくことで、どのくらいでI/Oが限界にくるかがわかるようになるからである。
- I/OサブシステムにおけるI/Oの性能を把握しておくこと(bonnie++やIOMeter)
- iostatのw/sとr/sの傾向を見て、IOPSの増加傾向を把握すること
- 特に、データファイルとログファイルのディスクが分離されていると、IOパターン毎のIO回数が把握しやすくなり、どのI/Oパターンに対して強化をしていくかが検討しやすくなる。
ディスクへのIO(特に回数)はネックになりやすく、これはMySQLやOracleといったDBだけでなく、他のミドル全般にいえる。従って、ディスクの性能状況については特に注意深くモニタリングし、問題の発生を事前に予見できるようにしておく必要がある。
封筒裏の計算の大切さ
性能やスケーラビリティが求められるシステムでは、どこにどれくらいの性能オーダーが必要なのかを検討するのは、アーキテクチャを検討する上でとても重要になる。封筒裏の計算の段階で大体のオーダーがわかれば、必要になるSWのアーキテクチャ、アルゴリズム、ハードがある程度はわかり、そのレベルで破綻していないことは事前に検証ができる。
それらの大体のオーダーについて、以下の3つのblogではまとめている。開発/インフラ運用の両方で、どこでどの程度の性能オーダーになっているのかを理解できていると、破綻のしない開発/運用が行えると思う。
- http://highscalability.com/blog/2011/1/26/google-pro-tip-use-back-of-the-envelope-calculations-to-choo.html
- http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait
- http://nippondanji.blogspot.com/2009/03/ssd-fusion-io-iodrive-duopci-expiress.html
ハードも年々進化していくわけで、どのようなアーキテクチャでどのようなオーダーで、どの部分が動作するのかについては、意識してウォッチしていきたいと思う今日この頃。
L1 cache reference 0.5 ns Branch mispredict 5 ns L2 cache reference 7 ns Mutex lock/unlock 100 ns Main memory reference 100 ns Compress 1K bytes with Zippy 10,000 ns Send 2K bytes over 1 Gbps network 20,000 ns Read 1 MB sequentially from memory 250,000 ns Round trip within same datacenter 500,000 ns Disk seek 10,000,000 ns Read 1 MB sequentially from network 10,000,000 ns Read 1 MB sequentially from disk 30,000,000 ns Send packet CA->Netherlands->CA 150,000,000 ns
ChromeのVimiumでスムーズにスクロールするようにさせてみた
Vimperatorのsmoozieをvimiumに移植しました。vimiumのscroll step sizeの設定は、400くらいにすると結構快適に使えます。
https://github.com/dann/vimium
これでvimumでもスクロールがスムーズになり快適に使えるようになりました。
はてなブックマークとInstantPaperを連携させてみた
LDR -> はてなブックマーク -> EverNote というワークフローで情報の蓄積をしているんですが、普段Kindleを使っているので、ゆっくり読みたい物についてはKindleで読むのもいいかもしれないと思うようになりました。
そこで、はてなブックマークに登録したエントリをInstantPaperに送るスクリプトを作ってみました。InstantPaperからはKindleにエントリを送信することができる機能があるので、これでKindleまでエントリを送ることができるようになります。
InstantPaperからKindleにおくるための設定
InstantPaperのページに詳細は書いてありますが、InstantPaperからKindleに配送するためには、事前に以下の設定をしておく必要があります。詳しくはInstantPaperのページで。
コード
https://github.com/dann/hatebu2instantpaper
#!/usr/bin/env perl use strict; use warnings; use utf8; use Config::Pit; use Carp (); use LWP::UserAgent; use URI::QueryParam; use XML::RSS; use Scalar::Util qw(reftype); use List::MoreUtils qw(any); # Config my $HATEBU_API_ENDPINT = "http://b.hatena.ne.jp/dann/rss"; my $HATEBU_TAG = "tokindle"; # Main my $INSTANT_PAPER_API_ENDPOINT = "https://www.instapaper.com/api/add"; my $CONFIG; main(); exit; # Logic sub main { init(); my $entries = fetch_entriies_from_hatebu(); post_entries_to_instantpaper($entries); } sub init { $CONFIG = pit_get( "hatebu2instant", require => { "instant_username" => "your username for instantpaper", "instant_password" => "your password for instantpaper", } ); die 'pit_get failed.' if !%$CONFIG; } sub fetch_entriies_from_hatebu { say("--> Fetching entries from HatenaBookmark"); my $ua = LWP::UserAgent->new; my $hatebu_api_endpoint = URI->new($HATEBU_API_ENDPINT); my $res = $ua->get($HATEBU_API_ENDPINT); my $rss = _parse_rss( $res->content ); return $rss->{'items'} || []; } sub _parse_rss { my $rss_text = shift; my $rss = XML::RSS->new; eval { $rss->parse($rss_text) } or Carp::croak( "Parsing content failed: " . $@ ); $rss; } sub post_entries_to_instantpaper { my $entries = shift; say("--> Posting HatenaBookmark entries to InstantPaper ... "); foreach my $entry (@$entries) { my $tags = $entry->{'dc'}->{'subject'} || []; $tags = [$tags] unless reftype $tags; my $contain_tag = any { $_ eq $HATEBU_TAG } @{ $tags }; post_entry_to_instanpaper($entry) if $contain_tag; } } sub say { my $text = shift; print "$text\n"; } sub post_entry_to_instanpaper { my $entry = shift; say("==> Posting '$entry->{title}'"); my $ua = LWP::UserAgent->new; my $endpoint_uri = URI->new($INSTANT_PAPER_API_ENDPOINT); $endpoint_uri->query_form_hash( username => $CONFIG->{instant_username}, password => $CONFIG->{instant_password}, url => $entry->{link}, ); my $res = $ua->get($endpoint_uri); unless ($res->is_success) { print STDERR $res->status_line, "\n"; } }
# ついでにPlaggerのPlugin化もしてみました
https://github.com/dann/p5-plagger-plugin-publish-instantpaper