railsマルチプロセスとログとlogrotate

先日パーティにお招きいただいたM社の方から、mongrel_clusterで複数プロセス立ち上げている時、logroateすると、ひとつのプロセスを残して、ログファイルを見失って正常に動作できなくなる、というご相談をいただきました。

美味しいお酒をいっぱいのませていただいたのでちょうど僕も使うアテがあるので、僕がよく使っているthinのケースを書いてみます。たぶんmongrelでもそんなに変わらないんじゃないかな〜。

マルチプロセスで起動する

・セッションをファイルじゃなくてDBに保存するようにする。やり方はググれば出てくるので省略。
・ログはproduction.logじゃなくてSTDERRに出力して、thinの方でファイルに書いてもらう。具体的には、config/environments/production.rbとかで

  config.logger = Logger.new(STDOUT)

という具合に。
・thinの設定

$ sudo ln -s /var/www/example/api/config/thin.conf /etc/thin/example.yaml
$ sudo emacs /var/www/example/api/config/thin.conf
---
chdir: /var/www/example/api
environment: production
servers: 3
address: 0.0.0.0
port: 3000
timeout: 30
log: log/thin_example.log
pid: tmp/pids/thin_example.pid
max_conns: 1024
max_persistent_conns: 100
require: []
wait: 30
daemonize: true

これでlocalhost:3000,3001,3002と3プロセスが立ち上がります。
ログは、

$ ls -la api/log/
-rw-r--r--  1 root    root     3081 Aug  6 20:42 thin_example.3000.log
-rw-r--r--  1 root    root     1292 Aug  6 20:42 thin_example.3001.log
-rw-r--r--  1 root    root    15632 Aug  6 20:42 thin_example.3002.log

という具合にプロセスごとに別々に出力されます。

Rails3.2からログの行が他プロセスのものと混ざるようになった件について – 昼メシ物語 などを見ていると、一つのファイルにかいた時ログが混じるという話が出ているのですけど、そもそも複数プロセスから一個のファイルに書き込む時点で怖くて仕方ないので、
最初から分けてあったほうがいっそスッキリするんじゃないかな〜。

ログが複数にわかれていても、

tail -f log/thin_example.*.log

とすれば一つの窓で監視することができます。

ロードバランサー(apache)の設定

 こんなかんじで

$ sudo ln -s /var/www/example/api/config/httpd.conf /etc/httpd/conf.d/example.conf
$ emacs /var/www/example/api/config/httpd.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot "/var/www/example/html/"
ServerAdmin mogya+example@mogya.com
ErrorLog "/var/www/example/log/error_log"
TransferLog "/var/www/example/log/access_log"
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog /var/www/example/log/custom_log combined
ProxyPass /api/v2 balancer://example
ProxyPassReverse /api/v2 balancer://example
</VirtualHost>
<Proxy balancer://example>
BalancerMember http://localhost:3000
BalancerMember http://localhost:3001
BalancerMember http://localhost:3002
</Proxy>

いまどきapacheなんて・・・という方はherokuでもAWSでもお好きなフロントエンドでどうぞ。

logrotate

sudo ln -s /var/www/example/api/config/logrotate.conf /etc/logrotate.d/example
emacs /var/www/example/api/config/logrotate.conf
/var/www/example/api/log/* {
daily
missingok
rotate 1000
notifempty
copytruncate
create 0666 root root
sharedscripts
postrotate
/etc/init.d/thin restart > /dev/null
/etc/init.d/httpd restart > /dev/null
endscript
dateext
}

thinだけrestartだと直後の挙動が怪しかったので、httpdもrestartするようにしてあげたほうがいいみたいです。

$ sudo logrotate -dv /etc/logrotate.d/example

で文法チェック。

$ sudo logrotate -f /etc/logrotate.d/example

で動作確認を行うことができます。

環境

$ rails -v
Rails 3.2.13
$ thin -v
thin 1.5.1 codename Straight Razor
$ httpd -v
Server version: Apache/2.2.24 (Unix)
Server built:   May 20 2013 21:12:45

Evernote Devcup Workshop “世界に伝わる製品紹介ビデオの作り方”

6月17日(月) Evernote Devcup Workshop “世界に伝わる製品紹介ビデオの作り方”で聞いてきた内容のメモ。

概論

*「何を伝えたいのか」が重要

*「説明書」を作らない

  • 全部の機能を順番に説明して「で、何?」ってなる動画のこと
  • 技術から発想したアプリが陥りやすい
  • 「どういう問題があって、それをどう解決するのか」を伝える

*動画の長さ

  • 経験的に、1分半を超えると離脱率が上昇する
    • これを超える動画は何らかの形でユーザーを飽きさせない工夫が必要。男性向けだったら女の子を出すとかw
  • 短いほうが簡単に思えるかもしれないけど、短くするほうが難しい。
    • 「何を伝えたいのか」がちゃんと決まっていないと短くできない
    • テレビCM:15秒の動画がどれだけ難しいか。

*どんな動画がいいか困ったら?

  • 似たようなサービスの動画をみてみる
  • MOVAAA!!(モバー)というサービスがあるよ!

動画の要素

*開発者がカメラの前に立って話す

  • 一番簡単で、言いたいことも結構伝わる
  • 注意事項:冒頭で挨拶とか自己紹介をしない。そんなことは見ている人にとってどうでもいい

*iPhoneやPCのキャプチャ

*実写

  • 冒頭と最後に人を映すと印象的な動画になる。画面キャプチャだけの動画は、審査員にとって全部一緒に見えてしまう。
  • 人物が歩いてきてiphoneで何かする程度なら、意外と撮れる
  • 権利関係注意(人の顔や広告物はぼかしておかないと、ツッコミが入った時、動画を削除せざるを得なくなる)

*テロップ

  • 要所に入れるとインパクトが強くなる
  • iMovieでも出来るよ

*BGM

*ナレーション

*効果音

*以下、プロじゃないとたぶん無理な内容

  • 実写とアニメの合成
  • アニメーション
  • タイムラプス(静止画をひたすら撮って動画にする)
  • インフォグラフィックス
  • 音楽の編集
    • テンポのいい曲はズレが目立つので、編集できない素人には難しい。BGM的な音楽がオススメ

ぶっちゃけプロに頼むといくらかかるの?

  • MOBERCIAL(モバーシャル)の中の人の実例と内心

    • 五万円(今回のイベント参加者から五組限定出血価格!)
    • 数十万円(ベンチャー支援的な気持ちで制作した)
    • 数百万円代前半(クライアントの社員が出演したりして制作費を抑えてこれくらい)
    • 数百万円代後半(出演者も調達するとこれくらいになる)
  • 実際にはもっとナマナマしく、あの会社のこの動画がいくら位、とか聞いたのですけど、もちろんオフレコw

[cakePHP]findの戻り値からモデル名を除去する

cakephpでModel->find(‘all’); をつかうと、検索結果を取得することができる。
この結果を

$this->set('results',$this->Model->find('all'));
$this->set('status','ok');

という具合にパラメタにセットして、

 protected function return_as_json(){
$this->header('Content-Type: text/html');
echo $this->indent( json_encode($this->viewVars) );
exit();
}

というような関数を呼び出してあげれば、検索結果をJSONで返すWebAPI的なものを作ることができる。

ところがこれがちょっと変で。JavaScriptからみた戻り値はこんなふうに見える。

{
"results": [
{
"Model": {
"id": "5",
"user_id": "8",
   :(略)
}
},
{
"Model": {
"id": "4",
"user_id": "8",
   :(略)
}
},
],
"status": "ok"
}

普通APIで検索結果を取得する時って、普通こうなってません?

{
"results": [
{
"id": "5",
"user_id": "8",
   :(略)
},
{
"id": "4",
"user_id": "8",
   :(略)
},
],
"status": "ok"
}

呼び出し側のJavaScriptから見たら、

function(res){//success
var models = res['results'];
for (var i = 0; i < models.length; i++) {
console.log(models[i]['Model'].id)
}
}

って謎の型名をつけるよりも、

function(res){//success
var models = res['results'];
for (var i = 0; i < models.length; i++) {
console.log(models[i].id)
}
}

のほうが自然ですよね。

前置きが長くなったけれど、そういうわけで型名を付けないで検索結果を返すようにする実装。
本当はjson_encodeするときに型名を落とすのが適切だと思うのだけれど、うまいやり方を思いつかなかったので、modelのカスタムfindとして定義してみた。

連想配列のまま構造を変えるのではなくて、一旦フラットにしてテキストとしてモデル名を除去しています。

allの代わりにnoModelNameを使えば、モデル名部分のないハッシュを得ることができます。

 $this->set('results', $this->Model->find('noModelName') );
$this->return_as_json();

[JavaScript]変数の存在確認

・同じ比較方法の場合、if文よりも三項演算子の方がやや速い
・もっとも高速な方法は論理和を使う方法
・もっとも時間がかかる方法は型比較をif文で行う方法
・null比較、型比較で三項演算子を利用すると真偽によって速度が(割と)変わる
( 変数の存在を確認する方法と速度比較(その2) – blog.katsuma.tv)

五年前の記事なのだけれど、関数に渡ってきた引数を存在チェックするとき、どう書くのが早いだろう?という実験レポート。
このレポートによると、

var ret;
if(arg) {
ret = arg;
} else {
ret = 'bar';
}

という比較的普通のコードと比べて

var ret = (arg!=null)? arg : 'bar';

三項演算子を使うと倍近く早いらしい。

でも、変数の初期化だったら僕はこう書くことが多い。

if(!arg) { arg = 'bar';}

ということで、五年経った現状と、僕のコードどうなんだろう、というのを見たいと思ったので試してみた。

Google Chrome 26.0.1410.65

[checkStrTernary]   1786
[checkStrIf]    1786
[checkArgsLogicalAdd]    49
[checkArgsTernary]   45
[checkArgsIf]   51
[checkArgsIf2]  48
[checkNullTernary]  74
[checkNullIf]   59
[checkTypeTernary]  61
[checkTypeIf]   48

待てw

やるんじゃないかとは思っていましたが、実質的に何もしていないことを見ぬいて最適化された結果、Date()の分解能テストと化しています。たぶんループが丸ごと削除されていますね。
もちろん、checkStrTernaryとcheckStrIf以外の順位は実施のたびに入れ替わりますので、個別のコードの有利不利を判定することはできません。

FireFox 17.0.1

[checkStrTernary]   14555
[checkStrIf]    14263
[checkArgsLogicalAdd]    10572
[checkArgsTernary]   10674
[checkArgsIf]   11215
[checkArgsIf2]  11258
[checkNullTernary]  10659
[checkNullIf]   10750
[checkTypeTernary]  11102
[checkTypeIf]   11714

うむ。さすが高機能JavaScript開発環境(僕命名)。
これを見ていると、やっぱりcheckStrTernaryとcheckStrIfが遅いものの、あとはそれほど大きな差じゃない程度になっています。checkArgsIfとcheckArgsIf2などは実施のたびに早いほうが入れ替わるので、誤差の範囲内です。

InternetExplorer 9.0.8112.16421

ログ: [checkStrTernary]   11868
ログ: [checkStrIf]    11766
ログ: [checkArgsLogicalAdd]    8618
ログ: [checkArgsTernary]   8525
ログ: [checkArgsIf]   10521
ログ: [checkArgsIf2]  10767
ログ: [checkNullTernary]  10777
ログ: [checkNullIf]   10655
ログ: [checkTypeTernary]  11507
ログ: [checkTypeIf]   10584

おおむねFireFoxと同じ傾向が出ています。よほど変な書き方をしない限り大差ないということですね。

所感

近年のブラウザにおいては、最適化が進んだ結果、以前ほど三項演算子有利ではなくなってきました。
それでも1割程度の差がつくので、シビアな場面ではトライする価値があるかもしれないですが、普通にコード書くときは、読みやすいほうを使うのでいいんじゃないかなーと思いました。

コード:

FacebookSDKがCakePHPのSessionを使うようにする

cakePHPでFacebookログイン[1/2] – もぎゃろぐで書いたとおり、
FacebookSDKをつかうと簡単にfacebookログインを実装することができます。,

FacebookSDKは、この簡単実装を実現するために、内部で$_SESSIONを呼び出して、accessTokenや CSRF対策のstate変数を保存しています。
この実装は、単純なWEBサーバであれば困らないのですが、複数のWEBサーバで分散処理するためにセッションをDBに保存する、というような設定をしている場合、 このままだと動かないことが予想されます。

CakePHPは、そういうときのためにセッションをデータベースに保存したりすることができるようになっていますので、FacebookSDKの変数も、CakePHPのセッションを使ってもらうようにしたいところです。

facebookのエンジニアの人たちは優秀なので、こういう用途も想定してSDKを書かれたようです。

facebookSDKのFacebookクラスは、ほとんどの実装がbase_facebook.phpのBaseFacebookクラスで実装されて、上記セッション変数の処理だけが、facebook.phpのFacebookクラスで実装されています。
なので、facebook.phpをコピーしたcakeFacebook.phpを作って、$_SESSIONの部分をcakePHPのCakeSessionに書き換えたCakeFacebookクラスを用意すれば、FacebookSDKもcakePHPの設定に従ってくれるようになります。

ということで出来上がったのがこちらになります。

facebook.phpと同じディレクトリに配置して

App::import(‘Vendor’, ‘facebook/cakeFacebook’);

$this->facebook = new CakeFacebook(array(
‘appId’ => Configure::read(‘facebook.appid’),
‘secret’ => Configure::read(‘facebook.secret’),
));

という具合にして使います。あとは通常のFacebookクラスと同じ使い方でOKです。

cakePHPでFacebookログイン

前の記事で予想がついている方もおられるかと思いますが、facebookログインするサイトを作っています。
RubyじゃなくてPHPで。人生何が起こるかわからないものですね。

さすがに生のPHPは書いてられないので、CakePHPを使っています。
ということで、今日の記事は、CakePHP+FacebookSDKです。
ぐぐれば似たような記事はいくつか出てくるのですが、いずれも記述が古くて動かなかったので、僕バージョンをまとめました。これも半年位すると動かなくなるのかもしれないですが。

さて、PHPでFacebookのOAuth2.0ログインを実装するやり方については、公式サイトに記述があります。

Login for Server-side Apps – Facebook Developers

ただこれだとAPI呼び出しが面倒なので、Facebook公式のfacebook-php-sdkをつかいます。
facebook-php-sdkの利用サンプルも、ぐぐればたくさん出てきますが、その大半は古い内容で現在のSDKでは動きません(getAccessToken()をわざわざ呼び出しているのは古いコードです。)
Migrating to OAuth 2.0 update: PHP SDK v.3.1.1- Facebook Developersを参考に書き直したのがこちら。

要するに、$facebook->getUser()して取れたらログイン成功、取れなかったらログインURLにリダイレクトすればOKです。昔のSDKで必要だったgetAccessTokenの呼び出しは、SDKが内部で処理してくれるようになりました。
コールバック引数を見てアレコレする部分もSDKが面倒を見てくれるので、login.phpとcallback.phpのように二つのファイルを作る必要すらありません。PHP用のSDKらしい実装ですね。

これを踏まえて、CakePHPでFacebookContollerを書いたのがこちら。

  • facebook SDKは、/app/Vendor/facebook/の下に配置。
  • appidとsecretは、bootstrap.phpに
    Configure::write('facebook',array(
    'appid'=>'***',
    'secret'=>'***'
    ));
    

    という具合に。

一応loginとcallbackを分けておきましたが、getUser()部分をbeforeFilterに移動させれば、ひとつのメソッドにまとめてしまうことも可能だと思います。
もっといえば、FacebookControllerというクラスを用意しなくても、ほかのControllerのbeforeFilterに書いてしまってもかまわないかも知れないですね。

[iOS] タブを残さずURLスキームを開く

TitaniumのユーザーBBSで、ChatWork のiPhoneアプリの挙動が話題になっています。

ChatWork の謎 | Don’t Fall – Titanium Mobileユーザー会サポートBBS

ChatWorkアプリでは、facebook認証でアプリを使うことが出来るのですが、これまでよく使われていたWebView上での認証ではなくて、Ti.Platform.openURL() を使ってブラウザを立ち上げてfacebook認証を行っています。

WebViewを使わない理由は、「OAuthの認証にWebViewを使うのはやめよう – Shogo’s Blog」に書かれているとおりだと思います。具体的な実装としてChatWorkアプリはとても参考になりますね。

さて、ブラウザ上でfacebook認証が成功すると、ChatWorkサーバはURLスキームを使ってChatWorkアプリケーションを起動させます。
ここがマニアックに興味深いところです。

  • ユーザーはリンクをクリックしていないのに、なぜURLスキームが起動したのか?
  • ChatWorkからブラウザに戻ったとき、URLスキームを起動したWEBページが残っていないのは何故?他のアプリだと残っちゃうよね?

結論から言うと、こういうふうに書けば同じ挙動が再現できました。

(本当は”chatwork:///”でChatWorkアプリが起動するのですけど、テストのために意味不明な起動を繰り返すとご迷惑になりそうだったので、メッセージアプリを起動しています)

ページを読み込むと同時にURLスキームを起動させる

 URLスキームは普通、aタグのhrefに書いて、ユーザーさんにクリックしてもらって使います。例えば、

<a href="sms:///">sms</a>

と書かれたHTMLのリンクをユーザーさんがクリックすると、メッセージアプリが立ち上がります。でも、facebook認証が済んだら、ユーザーさんの手を煩わせることなくアプリケーションに戻ってほしいですよね。

 location.hrefで強制移動とか、aタグを書いてjQuery(hoge).click();とか、いろいろ試してみたのですけど、そういうのは駄目なようです。
ページ内に動的にiFrameを生成してURLスキームを持たせることで上手く起動させることが出来ました。

URLスキームを起動したあと自分を閉じる

 URLスキームでアプリを起動したのはいいものの、そのHTMLがブラウザ上に残ったままになっていると、あとでブラウザを開き直した時に変なページが見えてしまったり、場合によっては勝手にアプリが立ち上がってイラっとさせられることがあります。結構有名なアプリでもこの現象起こしますよね。

 JavaScriptで自分を表示しているタブを閉じる場合、理屈上はwindow.close()で出来るのですけど、safariのwindow.closeはjavascriptで開いたページしかclose出来ないので、このようなごまかしテクニックを使うことで自分をcloseすることが出来ます。

window.opener = window;
var win = window.open(location.href,”_self”);
win.close();

URLスキームとwindow.close、どっちかが他方を妨害しそうな気がしますけど、実際には両方上手く動くみたいです。

P.S 1:自分でJavaScript書いて同様の挙動を再現させることに成功しただけなので、ChatWorkさんのアプリと厳密には一致しないかも知れません。
P.S 2:[iOS]って書いてますが、URLスキームをintentに変えればAndroidでもそのまま動作しました。
P.S 3:ChatWorkの中の方、出来るだけ迷惑かけないように調査したつもりですけど、不審なログとして調査対象に上がっていたりしたらごめんなさい。

[GoogleMapsAPI] APIと組み合わせる時のイベントはidleを使う

gmap.jpg

四隅の緯度経度を渡したら範囲内のスポット情報が得られる、というAPIは最近そこら中にあって、

これとGoogleMapsAPIを組み合わせて地図上に何らかのスポットを表示する、というのもそこら中で行われています。

こういうサービスを実現するためには、ユーザーが地図の表示位置を変更した時、新しい位置のスポット情報を取得して表示するためにAPI呼び出しをする必要があります。
この実装例をググると、bounds_changed(領域が変化した時)やdragned(ドラッグが終了した時)イベントのタイミングでAPIを呼び出すようにしている実装が多いのですけど、idleイベントを使うとすごい綺麗に実装することが出来ます。
以下、bounds_changedやdragnedを使った場合に発生する問題と、idleイベントだと何がうれしいのかをまとめました。

bounds_changedを使った時の問題

bounds_changedイベントは、「This event is fired when the viewport bounds have changed.」と定義されていて、地図の表示領域が変更になった場合に発生するイベントです。
このタイミングでAPIを呼び出すようにすれば、ユーザーが地図をドラッグした時も、ズームが変更された時もイベントが発生するので、わりといい感じに動くように思えます。

bounds_changedイベントの問題は、発生頻度が多すぎることです。bounds_changedは領域が変わるたびに呼ばれるので、ユーザーが地図をドラッグすると、一秒に数回の勢いでbounds_changedイベントが発生し続けます。
つまり、単純に実装すると、秒間数回のペースでAPIを呼び出し続けることになります。サーバ負荷の観点から絶対止めてほしいですし、ブラウザ側だって、そんな頻度で結果が帰ってきたら処理しきれなくてブラウザが固まってしまうことが予想されます。

カウンタをもうけて10回に一回だけAPIを呼び出すとか、タイマーを使って何秒に一回だけAPIを呼び出すとか、やってやれないことはないのですが、ずいぶん複雑なコードになってしまいます。

dragendを使った場合の問題

dragendイベントでAPIを呼び出すという実装も考えられます。ドラッグ終了時に一回だけ発生するので、その点はいいのですけど、今度はイベントの発生が少なすぎて弊害が発生します。

ズーム変更の時スポットが更新されない

さすがにこれは誰でも気づくので、dragendとzoom_changedでAPIを呼び出す実装にする人が多いと思います。

ドラッグが終わるまでAPIが呼び出せない

dragnedイベントはマウスを放すまで発生しないので、地図をドラッグしながら「この辺かなー?」って考えてもスポットが更新されません。ダメじゃないですけど、ユーザーフレンドリーじゃないですよね。

キーボードで地図を移動させた時APIが呼び出せない

そんな人はあまりいないと思うけど、GoogleMapsはキーボードでも操作ができるようになっています。この場合dragendイベントは発生しません。
また、最近のGoogleMapsは駅などをクリックするとその部分が中央に移動して詳細がポップアップするようになっていますが、この移動の場合もdragendイベントは発生しません。

自分でsetCenterイベントを呼んだ時も発生しない

これが最大の問題で、例えば検索フィールドを作って入力された住所の位置に移動するような実装をする場合、ジオコーディングして得られた緯度経度にmap.setCenterすることになると思うのですけど、この場合もdragendイベントは発生しません。
setCenterを呼び出す時は自前でAPI呼び出しをするコードを追加して・・・(後日APIが変更になった時、片方だけ変更しわすれてバグを埋め込むパターン)

dragendを使った場合の問題

こんな具合にして、起こりうる各種問題に場当たりの対応を繰り返すと、複数のイベントを組み合わせてフラグ管理をすることになったり、コード中のあちこちからAPIを呼び出すことになったり、どうしても複雑なコードになってしまいます。
実際僕もそうやって書いていたのですけど、ある時、idleイベントで全部カバーできることに気づきました。

idleイベントは「This event is fired when the map becomes idle after panning or zooming.」と定義されています。

実際ログを入れて試してみるとわかりますが、

  • Mapをnewすると、地図の表示が終わった時点で一回発生します。つまりここで、最初の一回目のAPI呼び出しを行うことが出来ます
  • 地図をドラッグしおわった時に一回発生。ドラッグ中も、ちょっと手を止めるとそこで発生。idleにならないと発生しないので、API呼び出しが多くなりすぎる心配がありません。
  • ズーム変更の時も発生
  • キーボードで地図を動かした時も発生
  • setCenterやsetZoomを呼び出した時も移動直後に発生

と、理想的な挙動を示します。つまり、HTML表示直後の一回もマップ移動時のAPI呼び出しも、全部まとめて

google.maps.event.addListener(self.gmap, 'idle', function() {
//ここでAPI呼び出し
});

としておくだけで意図した動作をさせることが出来ます。

[TitaniumMobile]いろいろ便利関数集

TITANIUM_logo1_original.png

何となく間に合わない予感がしていたので先送りしていたTitanium mobile Advent Calendar 2012、前倒しで参加表明したら、案の定ネタが思い浮かばないうちに当日になってしまいました。
大丈夫。こういうときは、自作便利関数ライブラリを公開するといいっておじいちゃんが言ってたので、ボクのutil.jsの中身を公開します。


文字列をあれこれする便利関数

前後の空白を削除するとか、改行を落とすとか。

ちなみに、見落としがちですが、printfはGlobal.Stringとしてあらかじめ定義されています。

var_dump

PHPでいうところのvar_dump、オブジェクトの中身が見たいときは、

Ti.API.debug(JSON.stringify(obj));

という具合にすれば見られる。
Ti.API.debug(obj); で見られた牧歌的な時代もあったらしいけど、今試してみたらやっぱりダメだった。

速度の計測

できあがったアプリがなんか遅いのでチューニングが必要だ、となった場合、「推測するな,計測せよ」の原則に従って、まずどこに時間がかかっているのが鉄則です。
ということで、処理時間を計測するためのコード。

こういうふうにして使います。

var trace = new util.TraceLog;
trace.start('hoge handler');
//時間がかかってそうな処理1
trace.lap('hoge handler 1');
//時間がかかってそうな処理2
trace.lap('hoge handler 2');
//時間がかかってそうな処理3
trace.stop('hoge handler');

そうすると、こういうログが得られます。

12-08 02:07:14.035: D/TiAPI(25391): [hoge handler] has started
12-08 02:07:14.039: D/TiAPI(25391): [hoge handler 1] has passed at 2ms
12-08 02:07:14.137: D/TiAPI(25391): [hoge handler 2] has passed at 100ms
12-08 02:07:14.137: D/TiAPI(25391): [hoge handler] has finished at 100ms

処理に時間がかかっているのは「時間がかかってそうな処理2」の部分だ!ということが一目瞭然になります。
さらにtrace.lapをつっこんで犯人を絞り込むもよし、十分にわかったのであれば腕をふるって高速化に着手するもよしですね。

明日は@tady_jpさんですね。よろしくお願いしますー。

svgで画像ファイルを量産する

この記事は、Titanium mobile “early” Advent Calendar 2012の提供でお送りしております。

Titanium Mobileのおかげで、iPhoneとAndroid両にらみでアプリ開発することが現実的になったのはいいのですけど、どちらも機種が増える一方なので、画像ファイルの準備が洒落にならなくなりつつあります。
最低限必須となるアイコンとスプラッシュスクリーンだけで50種類!

Titaniumで用意する画像のサイズ – Sawalog

とはいえ、実際にはほとんど同じ画像がサイズの違うキャンバスに貼り付けてあるだけなので、自動化することにしました。

tiqiita.png

最大で1024pxの画像を57pxまで圧縮することになるので、ビットマップやpng画像をそのまま縮小すると目も当てられない画像になってしまいます。そこで、デザイナさんにはillustratorのベクター画像で納品をお願いすることにしました。
illustratorの「Webおよびデバイス用に保存」機能を使うと、作成していただいた画像をSVGフォーマットで出力することが出来ます。
illustrator.png

SVGは、ベクターグラフィックスなので縮小してもつぶれない、たいていのブラウザでも開くことが出来るなど、期待される要素がたくさんある割にはちっとも日の目を見ないフォーマットですが、それはさておき、プログラマ御用達の画像処理ツールImageMagickやそのRubyバインディングであるRMagickで扱うことが出来ます。

というわけでRubyで書いたコードがこちら。icon.svgとsplash_screen.svgを元に、必要なサイズに拡大縮小、背景は指定した色で塗りつぶした画像を一気に生成します。

こういうのを用意しておけば、「iPhone6が発売されたので新しい解像度の画像が必要」というときでも、プログラマレベルで片付けることが出来るので便利ですよね。

ただこの手法にはちょっと難点があって。ドロップシャドウとか非表示レイヤーなんかはImageMagickがついてこれないのでぐちゃぐちゃになってしまうことがあります。パスで描かれた単純な画像しか適用できないのが辛いので、最近はPhotoShopのアクション機能で出力するほうがいいのかなーとか考えています。そのへんも手法が確立したらそのうちブログで書こうと思います。