というようなことを元々は teck.kayac.com Advent Calender 2013 の17日目 の記事として考えてたんだけど、結局なんか無性にWicketの事を書きたくなっちゃったので、お蔵入りになった記事をこっちに書く事にする。
カレンダーのノリで書いてたのをそのまま持ってくると大分寒い感じがするが、面倒なので不自然なカレンダー要素を消す意外は無編集で。
ndenv を homebrew で入れる話 または如何にして心配するのを止めてRequireJSを愛するようになったか
Advent Calenderネタの捏造のため紆余曲折あって週末Backbone.jsのexampleを永続化部分をローカルストレージじゃなくてAPIベースに書き換えつつテストをコマンドライン(Mocha on node)でやる、みたいなことをやってました。
その副産物としてndenvをhomebrewで入れられるようforkして弄りつつ、Formulaを書いたりしました。
brew tap mix3/ndenv
brew install ndenv node-build --HEAD
こんな感じで入れられると思います --HEAD なのはお察しください。多分他の誰かが既にやってるんじゃないかと思いますが気にしない事にしました。
先生助けてっ Mochaが息してないの!
で、node入れてbackbone-examplesを弄ったわけですがAPIベースにするのは割と簡単に出来る(Backboneがモデルの永続化にRESTが使える)のですが「さてRequireJSで分割したのだし何かテスト書くか」と適当に書くとあら不思議。Mochaでテストが全くが実行されません。
AMD準拠のmochaテストをWebとCUIで共通化 - それなりブログにあるように非同期関連でうまく行かないようで同ブログにて書いてある通り「amdefineで解決や!」と思ったらcollectionのテストの時にmodelを呼ぼうとして「みつかんねーよ!」と怒られてRequireJSの時と違ってbaseUrl設定してないんだし、そうだよなってなって奇声上げてました。
reqirejs で分割した後、mochaでcliテスト書こうとしたら全くテスト走らないので、amdefine 使ったらbaseUrl の関係でエラるしキエェェェーーー!!!ってなってる
— mix3 (@mix3) 2013, 12月 15
で色々見ててどうにもならない感じだったけど、なんか他のサンプルとか色々眺めてるとテストの中でrequirejs使ってるのが多いっぽかったので、どうもそっちの方が一般的のようです。
describe("Did it run?", function(){
it("model is a model", function(done){
// こんな感じで中でrequirejsを呼ぶ
requirejs(["model"], function(model) {
expect(model).to.be.a("model");
done();
});
});
});
無理せずrequirejs使っていきましょう。
もう一つテストで困るのが、コマンドラインテストだとajaxとかで怒られるということ。(domが無いからってことで良いんだよね多分)
- NilsLattek/backbone-requirejs-node-boilerplate
- Testing jQuery code with Mocha, Chai and jsdom | Netboy
この辺りをコピって参考にしてjsdomを使うようにしてあげると良いようです。
ということで出来たのがこちらになります。
どうやってコマンドラインでテストするかばかり考えていて「Backbonアプリのテストってどんな感じで書けば良いのか」についてまでたどり着かなかったので次触るときはそこまで考えて触りたいものですね。
まとめ
- 実は一番最初の発端は「AngularJS触ろう!」だったのにどうしてこうなった。
- 今はMarionette.jsが気になります。
- js、結局クライアントとサーバで分かれてるのが少し不幸っぽい(小並感)
というようなことを元々は teck.kayac.com Advent Calender 2013 の17日目 の記事として考えてたんだけど、結局なんか無性にWicketの事を書きたくなっちゃったので、お蔵入りになった記事をこっちに書く事にする。
カレンダーのノリで書いてたのをそのまま持ってくると大分寒い感じがするが、面倒なので不自然なカレンダー要素を消す意外は無編集で。
勤怠管理してますか?
私はと言うとまあお察しください。そう、勤怠管理は面倒です。
- 何時に出社したか?
- 何時ににどれくらい休憩したか?
- 何時に退勤したか?
覚えてられないですね。退勤時に記録を付けるときにはもう記憶が曖昧です ><;
ということで退勤時間の自動検出ソリューションとか言って、適当に休憩や退勤の時間を記録するライフ八苦したいと思います。
いかにして自動的に記録するか
自分は最近 MacBookPro Retina 13インチ 16GBメモリ 512GB SSD というとても快適な環境で仕事してるのですが(16GBも積んでるとVirtualBox&Vagrantによる開発環境が本当に快適)
- 昼休憩はスクリーンセーバーで日々萌え萌え画像を垂れ流して「 頭おかしい人 」を演出
- 出社退社移動中はPC閉じてスリープ
という感じなので、スクリーンセーバーになってた時間とスリープしてた時間が後から分かれば休憩、退勤の時間が分かると言えます。(実際は業務内容と時間を紐づける必要がありますが、今のところ自分の業務内容は一つだけなので考えなくてOK。複数案件抱えてたりしてる人は本当に大変そうです…)
問題はスリープの状態は記録出来ないこと。記録付けたくてもPCが眠ってるのでPC自身がアクション起こせません。
ということでジョースター卿に教えを乞いました
なるほど!!!
ということでジョースター卿の教えを受けてPC稼働中は常に記録を取り続け、スクリーンセーバーの時は記録しない、という感じにやってあげるとうまく行きそうな気がしてきました。
具体的には?
homebrew で mysql を入れた後、適当に create database して適当に以下のようなテーブルを用意して
CREATE TABLE receive (
time datetime NOT NULL,
PRIMARY KEY (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
以下のようなスクリプトを書いてcronに設定
#!/bin/bash
if [ "" == "$(ps aux | grep ScreenSaverEngine | grep -v grep)" ]; then
/usr/local/opt/mysql/bin/mysql -u root beacon_receiver -e "insert into receive values (now());"
fi
* * * * * /bin/bash ~/.beacon.sh
するとこんな感じのスクリプトを用意ししてぺろっと出力すると
- 9時半に出社してる
- 13時15分ぐらいから昼飯行ったっぽい
- なんか 13:50 辺りでちょっと復帰してるの気になるけど…
- 20時45分ぐらいに退社してる
というのが分かって「 あらやだ便利! 」という感じになりました。ソース一式はこちら⇒https://gist.github.com/mix3/7973095
お試しあれ。
なお、日々ちゃんと勤怠管理出来てる人には関係ない話の模様。
前の記事が @fujiwara さんにブクマされた後もりもりっとブクマがのびてビビるなど。mix3です。
vagrantやansibleが流行っぽいので、MBP13インチをRetinaモデルに新調してウキウキハイテンションの今のうちに触っておくことにしました。
ちなみにpacker,vagrant,ansibleに対する自分の理解は以下のような感じ
- packer
- vagrantのboxを作るためのツール
- vagrant
- 昔virtualboxのフロントエンドツールだったぽいけど今はvirtualboxに縛られなくなってるんだっけ?
- ansible
- provisioningツール chefとかより軽量
正しくないかもしれないけど、大きく外してないと思いたい。
インストール
packerはbrew install packerか、バージョンを気にする場合はgit clone > make
vagrantはVagrant Downloadsからdmgを持ってきてインストールでサクっと完了。
ansibleはbrew install ansibleで。なおpyenvを使ってsystemのpythonを使われないようにしました。
packerを試す
やったのはcentos5.6のboxを作ること my-packer-template-files/centos6.4を元に弄りました
- vmwareの項目を削った
- netinstallだと時間がかかるのでDVD1of2をローカルに持ってきておいてそれを使うようにした
- Virutalboxで実際にインストールした後に出来るanaconda.cfgををもとにks.cfgを弄った
- GuestAdditionsのせいで削ったyumが元に戻った
- etc...
色々削れないかなぁと思って色々削った末に色々削れずに元に戻るということをpacker buildを打ちまくって繰り返してました。
なお、最小構成でのOSインストールにしているとはいえboxがexportされるまでそれなりに時間が掛かるのでtry&errorで試すのは結構面倒くさいです。
- box作成までの流れ
- vmが立ち上がってkickstartでOSインストール
- vm再起動後、provisionersの設定が走ってyumやら色々インストール
- vm落とした後box作成
ちなみにboxは Vagrantbox.esに色々あるのでそれで問題ないのであればpacker使って自作する前にそっちを使うべきかと思います。
centos5.6のboxもあります。
ということで自作したpacker templateはこちらになります my-packer-template-files/centos5.6
json手書きとかケツカンマの関係でやりたくなかったのでperlで書いてjsonに直す感じになってます
vagrant upする
boxが出来たので起動するのですが、いまいちvagrantの使い方がわかっていないのでとりあえず以下の手順で起動してみた
- vagrant box add [box name] [box file]
- cd 適当な場所
- vagrant init [box name]
- vagrant up
vagrant init でVagrantfileが作られるので設定が必要な場合はupするまえに弄る感じになるっぽい 例えばvirtualboxの設定を変えたいときは以下を弄ったりするんだと思われる
packerの時点でGuestAdditionsインストールを削って使わない設定にするとかもここかな?
# config.vm.provider :virtualbox do |vb|
# # Don't boot with headless mode
# vb.gui = true
#
# # Use VBoxManage to customize the VM. For example to change memory:
# vb.customize ["modifyvm", :id, "--memory", "1024"]
# end
sshで入るときは vagrant ssh すると入れる vagrant ssh-config で .ssh/config の設定が見えるので手動で何かしたいときはそっちを見ると良いかな
ansibleを試す
ansibleを動かすにはINVENTRY(サーバリスト)とplaybook(chefでいうレシピ)が必要になります。今回は配信先が一つなのでINVENTRYはシンプルに一行で良いのですが、意味も無くグルーピングしてみました
$ cat hosts
[vagrant]
127.0.0.1 ansible_ssh_port=2222
# きちんとグルーピング指定するならこんな感じかな?
# [vagrant-app]
# 127.0.0.1 ansible_ssh_port=2223
# 127.0.0.1 ansible_ssh_port=2224
# 127.0.0.1 ansible_ssh_port=2225
# 127.0.0.1 ansible_ssh_port=2226
# [vagrant-db]
# 127.0.0.1 ansible_ssh_port=2227
# [vagrant-app:vars]
# ...
# [vagrant-db:vars]
# ...
[vagrant:vars]
ansible_ssh_user=vagrant
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
ansible_*のオプションはAnsible in detail で確認しました
ひとまずはこれで配信が出来るか試したところ
$ ansible all -i hosts -m ping -s
127.0.0.1 | FAILED >> {
"failed": true,
"msg": "SUDO-SUCCESS-moxmhsptrmdnainrjekmubryetybkhrq\r\nError: ansible requires a json module, none found!",
"parsed": false
}
という感じで怒られてしまいました。配信先のpythonにjson moduleが別途必要なようです(python2.6からは不要)文言が違うけど以下参考
これはに対して -m raw でjsonを使わず -a "..." で実行内容を直接指定して配信先に対してjson moduleを入れるのが定石のようなようです
$ ansible all -i hosts -m raw -a "yum -y install python-simplejson" -s
$ ansible all -i hosts -m ping -s
127.0.0.1 | success >> {
"changed": false,
"ping": "pong"
}
これでplaybookを作ってやれば配信できる状態になったと思われます。
ちなみに ansible all -i hosts all -m ping -s のオプションの意味ですが以下のようになっています
$ ansible
Usage: ansible <host-pattern> [options] # host-pattern は all指定で全グループへ、グループ名指定で特定のグループへ配信
Options:
-i INVENTORY, --inventory-file=INVENTORY
specify inventory host file # ホストリストファイルの指定
(default=/usr/local/etc/ansible/hosts)
-s, --sudo run operations with sudo (nopasswd) # 配信先での操作でnopassのsudoを使う
playbook
playbookは以下をつまみ食いしたあと、後公式ドキュメン Ansible Modules をつまみ食いして作りました
作ったplaybookの内容は以下の通り なにやら怪しい感じですが気にしてはいけません。
- apacheのyumインストール
- fcgiのyumインストール
- mod_fastcgi2.4.6のソースインストール
- mysqld5.1のrpmインストール
- memcachedのyumインストール
ということで、具体的にはhttps://gist.github.com/mix3/7611961のようになりました 意味も無くymlを分割したりしています。
rpmモジュールはAnsible で RPM を扱うから拝借させてもらいました
本来必要の無いものなのですが、CentOS5だとyumモジュールでのrpmインストールがうまく行かないようなので使わせてもらいました
ということで
仮想化やらクラウドやらのおかげで複数台のサーバを気軽に扱えるような時代になってるので、気軽に扱えるなら扱えるなりに扱い方を覚えていかないとなぁとそんなこと思いながらpacker, vagrant, ansible とかを触ってみました。
次はserverspecとか触りたいですね。確か今年の新卒たちはchefやらserverspecやらを研修で学んだと聞くのでそろそろ後輩に追いつかないといかんとですよ。
isucon3スタッフとして本選運営のお手伝いをしてきましたが、参加者ではないのでisuconについて特に書くことが見つからないmix3です。一つ言えるのはみんな凄すぎでそれに比べて自分は本当になにも出来ない無能なんだなぁと再認識させられました。死にたい。
移植を引き受けた話
isuconでは最初に動く状態のアプリが用意されるのですが、そのアプリは問題が確定した後にまずリファレンス実装(今回はPerl)がされ、その後予選を抜けた人たちが使用していた言語(Perl含むRuby,Python,PHP,NodeJS,Go)に移植する、という流れで作られました。自分はその中でGo言語移植を担当しました。
ちなみにPerl以外の移植言語の中ではPHPぐらいしか触ったことがなく、PHPもかれこれ2,3年は触っておらずすっかり忘れてしまってるので、何の言語を担当しようと何も分からない状態から始まるという「なんで移植担当引き受けちゃったの?」と言われても仕方ないような状態でした。
なんでだろう?きっと暇だった仕事に余裕があったんだね
移植コトハジメ
そんな状態だったので、移植する前に「Goの勉強しないとなぁ」となり、以下のサイトを参考にGoの書き方、Goでwebappを作る場合にどうするのか調べたりしてました。なお、予選コードがあったおかげでベタ移植には困ることが無かったのでとてもとても助かりました。動くコードがあるというのは大事です。
- Go言語の初心者が見ると幸せになれる場所
- Go言語でWebAppの開発に必要なN個のこと
- 予選のコード
- jsonのconfigファイルのロードや、gorilla/muxなどは予選コードを
コピペ参考にしました
- jsonのconfigファイルのロードや、gorilla/muxなどは予選コードを
移植戦略
今回の移植で予選コードに追加して必要だったのは以下3点
- 外部コマンド
- 画像リサイズにリファレンス実装ではImageMagickのconvertコマンドを使用するため
- JSONのレスポンス生成
- 予選とは違いAPIベースだったのでJSONを返す必要があった
- ロングポーリングの実現
これらが出来れば後は予選コードをコピペ参考にすれば移植は出来そうでした
外部コマンド, JSONレスポンス
外部コマンドについてはパッケージドキュメント os/exec 見たまんまです。捻りようもありませんね。
JSONレスポンスはSuper-easy JSON HTTP responses, in Goをパクりました参考にしました。
へーと思ったのがtype Response map[string]interface{} の書き方。mapの値の型をinterface{}にするとinterface{}は何でも受け取れるので自身(この場合Response型)を入れ子にすることが出来るので、いわゆる連想配列やらMapやらと同じ感じで扱える型が出来るわけですね。まあJavaでMap<String, Object>とかする感じでしょうか。
goroutin サイコー
ロングポーリングはGo Language Patterns web/Long-Poll Serverにてgoroutinを使った例があり、これをパクりました参考にしました。
ここで始めてgoroutinを知りました。Goの話を聞くときは大抵並列の話が出るな、と思っていたのですが「なるほど」となりました。たしかにgoroutinすごいです。
- message/channel で排他制御とか考えずにデータをやり取り出来る
- selectでmessage/channelを通してイベントドリブン制御
- go func () { ... } () でさくっと並列処理を走らせる
確かにこれはそういった処理実装するの「簡単だわー」ってなります。
ただまあロングポーリング程度の実装で何が分かる?という向きもあると思うし、これでヒャッホーってなってプロダクション環境で使ったりした日には何か罠踏むんだろうな、とは思いますが…。
Kossyのfilterと例外処理
ここまでで移植は可能だったので割合サクっと移植してしまい本選でもそれをgoの初期アプリとして出しました。が、正直なところ本戦に出した移植アプリは移植としては不完全だったかなぁという思いがありました。
なぜならPerlのリファレンス実装で使われているKossyというフレームワークにはfilterという機能があり、Sinatraルーティングに追加してコントローラ処理の前後に処理を挟めるようになっており、リファレンス実装ではそれを使って、ユーザ情報の取得、ユーザ情報取得失敗時の400エラーを実装していました。抜粋すると以下のような感じ。
filter get_user => sub {
# userの取得してstashに保存 別にundefでも構わない
};
filter require_user => sub {
# stashにuserが無かったら400エラー
};
post '/signup' => sub {
};
get '/image/:image' => [qw/ get_user /] => sub {
# 先に get_user が実行される
};
get '/me' => [qw/ get_user require_user /] => sub {
# 先に get_user, require_user が並びの順で実行される
};
これに対してGoのベタ移植ではgetUser関数を呼んで nilチェックするコードを各コントローラにコピペしていました。「こんなの業務で書いたら殺されるかもしれない」という思いが渦巻くぐらいには良くない書き方だと思います。
それともう一つよく無かったなと思ったのがエラー時のレスポンス処理。Kossyでは$c->halt(500)などとしてExceptionを投げれば後はKossyがそれを捕まえてエラーレスポンスを返してくれます。これがGoのベタ移植では必要なときに毎度レスポンスを設定してreturnして関数を抜けるという素朴なことをしていました。
この「Kossyのfilter」「エラー時のレスポンス処理」2点が移植していてすごくもやもやしていたので、本選のアプリとしては使わないかもしれないけど「こうしたほうが良いんじゃないか?」というのを盛りこんだ実装も別で書いたりしていました
Goにおける例外処理
Goにはtry-catch-finallyがない代わりにpanic/recoverというものがあり、deferと組み合わせて例外処理っぽいことが出来ます Defer, Panic, and Recover
- defer
- 関数を抜けた後に実行する遅延処理を定義出来る
- panic
- 関数を即抜ける perlで言うとdieみたいな感じ またそのときに遅延処理を実行する
- recover
- panicから復帰する perlで言うとevalみたいな感じ deferの中で使うのが基本?
組み合わせて書くと例えば以下のような感じになります
func main() {
// (1) mainを抜けると実行される処理の定義
defer func() {
// (3) panicから復帰する
if err := recover(); err != nil {
fmt.Printf("recover: %v\n", err)
}
}()
// (2) mainから即抜ける
panic("panic")
}
// (4) output
// recover: panic
defer panic/recoverを使ったエラーレスポンス処理
これを使ってwebappのエラー処理をするならこうなるかな?というのを書いてみたのが以下になります(ちなみに以下のサンプルを書いていてswitch文にbreak使わなくてもよいことを初めて知りました)
package main
import (
"net/http"
"github.com/gorilla/mux"
"fmt"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler).Methods("GET")
r.HandleFunc("/{code}", handler).Methods("GET")
http.Handle("/", r)
http.ListenAndServe(":5000", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
defer func () {
if err := recover(); err != nil {
var code int
switch err {
case "400": code = http.StatusBadRequest
case "404": code = http.StatusNotFound
case "500": fallthrough
default : code = http.StatusInternalServerError
}
http.Error(w, http.StatusText(code), code)
}
} ()
vars := mux.Vars(r)
if code := vars["code"]; code != "" {
panic(code)
}
fmt.Fprint(w, "OK")
}
// [output]
// http://loclahost:5000/ => OK
// http://loclahost:5000/400 => Bad Request
// http://loclahost:5000/404 => Not Found
// http://loclahost:5000/500 => Internal Server Error
// http://loclahost:5000/999 => Internal Server Error
panicの引数はinterface{}型なのでつまり何でも渡せるので、自前の型を定義して便利機能を付けておくとrecover()で受け取った後が捗りそうですね。
例外処理自体はこれで良いのですが、ただこのままだとdeferをコントローラ毎に書いて行かないと行けなくなるので、当然それは嬉しくありません。
func main() {
r := mux.NewRouter()
r.HandleFunc("/hoge", hoge).Methods("GET")
r.HandleFunc("/fuga", fuga).Methods("GET")
r.HandleFunc("/bar", bar ).Methods("GET")
http.Handle("/", r)
http.ListenAndServe(":5000", nil)
}
func hoge(w http.ResponseWriter, r *http.Request) {
defer func () {
if err := recover(); err != nil {
// ...
}
} ()
fmt.Fprint(w, "hoge")
}
func fuga(w http.ResponseWriter, r *http.Request) {
defer func () {
if err := recover(); err != nil {
// ...
}
} ()
fmt.Fprint(w, "hoge")
}
func bar(w http.ResponseWriter, r *http.Request) {
.
.
.
// deferコピペしないといけないのでは嬉しく無い
で、どうしようか考えた結果、defer,recoverするラッパー関数にコントローラを渡してあげれば良いんじゃないかと考えてみました、具体的には以下のような感じ。
func main() {
r := mux.NewRouter()
r.HandleFunc("/hoge", wrapper(hoge).Methods("GET")
r.HandleFunc("/fuga", wrapper(fuga).Methods("GET")
r.HandleFunc("/bar", wrapper(bar ).Methods("GET")
http.Handle("/", r)
http.ListenAndServe(":5000", nil)
}
func wrapper(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
defer func () {
if err := recover(); err != nil {
// ....
}
} ()
handler(w, r)
}
}
func hoge(w http.ResponseWriter, r *http.Request) {
panic("hoge")
}
func fuga(w http.ResponseWriter, r *http.Request) {
panic("fuga")
}
func bar(w http.ResponseWriter, r *http.Request) {
.
.
.
こうすることで例外処理の復帰部分をラッパー関数に押し込むことができました。例外処理のためにHandleFuncに渡すコントローラはラッパー関数を必ず噛ませないといけないルールが出来てしまうので、そこはちょっとアレな感じですが。
そしてさらにこのラッパー関数を多段にするとKossyのfilterっぽいことが出来そうです。(Kossyでは配列で順序を自由に設定出来るので、実際にはそれっぽく出来るってだけですけども)
package main
import (
"net/http"
"github.com/gorilla/mux"
"fmt"
)
type Stash map[string]interface{}
func main() {
r := mux.NewRouter()
r.HandleFunc("/pass", base(pass(handler))).Methods("GET")
r.HandleFunc("/{code}", base( handler )).Methods("GET")
r.HandleFunc("/", base( handler )).Methods("GET")
http.Handle("/", r)
http.ListenAndServe(":5000", nil)
}
func base(handler func(w http.ResponseWriter, r *http.Request, s Stash)) func(w http.ResponseWriter, r *http.Request) {
return func (w http.ResponseWriter, r *http.Request) {
defer func () {
if err := recover(); err != nil {
var code int
switch err {
case "400": code = http.StatusBadRequest
case "404": code = http.StatusNotFound
case "500": fallthrough
default : code = http.StatusInternalServerError
}
http.Error(w, http.StatusText(code), code)
}
} ()
handler(w, r, Stash{})
}
}
func pass(handler func(w http.ResponseWriter, r *http.Request, s Stash)) func(w http.ResponseWriter, r *http.Request, s Stash) {
return func (w http.ResponseWriter, r *http.Request, s Stash) {
s["msg"] = "PASS"
handler(w, r, s)
}
}
func handler(w http.ResponseWriter, r *http.Request, s Stash) {
vars := mux.Vars(r)
if code := vars["code"]; code != "" {
panic(code)
}
if msg, ok := s["msg"]; !ok || msg == "" {
fmt.Fprint(w, "OK")
} else {
fmt.Fprint(w, msg)
}
}
// [output]
// http://loclahost:5000/ => OK
// http://loclahost:5000/pass => PASS
// http://loclahost:5000/400 => Bad Request
// http://loclahost:5000/404 => Not Found
// http://loclahost:5000/500 => Internal Server Error
// http://loclahost:5000/999 => Internal Server Error
どうでしょうか。ラッパー関数を多段にすることでfilterっぽい感じが表現出来たり、panic/recoverでエラーレスポンスをサクッと作れたり、なんとなくフレームワーク支援を受けて書いてるような気分に浸れそうな感じがしてきませんかね。
予選コードから考えるとちょっと手を入れ過ぎ感が否めなかったり、移植と言っても機能を満たせば良いわけでフレームワークがやってる処理までそれっぽく移植する必要はないので、結局は本戦の移植アプリにはしませんでしたが、実際にGoでこういうの書く場合はこれぐらいの工夫はしても良いんじゃないかなと思ったりします。ただでさえGoだとまじめにエラーハンドリングする必要があって縦にコードが伸び易いので、少しでもコードを減らす工夫はしたほうが良いように思います。
なお、これを適用することによって900行あった本戦の移植アプリが800行になりました。やったねたえちゃん!100行も減ったよ!でもperlはそもそも400行ありませんでしたね。切ない。
ということで
自己満足も満たしつつ移植作業が出来たので楽しかったです。なによりgoroutinを知れたのが良かったです。「Goが良いというのはこういうことか」みたいなのが少しだけ実感を持って感じられました。
おわりに
今回はisuconにスタッフとして関わったけれどもisucon自体には一度も参加してないので、来年isuconあったら予選ぐらいは参加してボコボコにされてこようかな、とかそんなこと考えるくらいにはisucon3の盛り上がりが凄かったです。
isucon関係者、参加者のみなさま、本当にお疲れさまでした。
アシュリーたんが可愛すぎて生きるのが辛い 任天堂こんな可愛いキャラクタいつの間に生み出してたんや mix3です
Photon Cloud が何とか良くわかってないんだけど、とりあえず試したかった
ちなみに Linux SDK は さくらVPS で 試したところ make すら出来ませんでした
Linux SDK には勝てなかったよ…
MacOSX SDK の Demo を動かす
- MacOSX SDK をサイトからダウンロードして unzip
- Demos/demo_loadBalancing/iMac/demo_iMac_loadBalancing_cpp.xcodeproj を XCode で開きビルドする
- $ Demos/demo_loadBalancing/iMac/build/debug/demo_iMac_loadBalancing_cpp
always:
0 - exit
--------------------
outside a game room:
1 - create game
2 - join random game
--------------------
inside a game room:
1 - leave game
--------------------
connecting
2013-10-23 02:06:56,558781 ERROR NetworkLogic.cpp connectionErrorReturn() line: 217 - code: 1039
connection failed with error 1039
2013-10-23 02:06:56,559703 INFO NetworkLogic.cpp disconnectReturn() line: 281 -
disconnected
- connection failed するので NetworkLogic.cppを弄る
- 80行目 : mLoadBalancingClient(*this, L"if connecting to the exitgamescloud, replace this string with the application id, that can be found after logging in at http://exitgamescloud.com/Dashboard", appVersion, PLAYER_NAME)
- 100行目 mLoadBalancingClient.connect(L"localhost:5055"); // specify the ip and port of your local masterserver here; call the parameterless overload instead, if you want to connect to the exitgamescloud
- 80行目はL"YOUR_APP_ID"に変更, 100行目は Cloud を使うので空に変更
- 再度ビルド
- $ Demos/demo_loadBalancing/iMac/build/debug/demo_iMac_loadBalancing_cpp
always:
0 - exit
--------------------
outside a game room:
1 - create game
2 - join random game
--------------------
inside a game room:
1 - leave game
--------------------
connecting
2013-10-23 02:18:49,198122 INFO LoadBalancingClient.cpp onStatusChanged() line: 510 - connected to masterserver
2013-10-23 02:18:49,507223 INFO LoadBalancingPeer.cpp opAuthenticate() line: 100 - OperationRequest - operationCode: 230 {224="YOUR_APP_ID", 220="1.0", 225="OS X"}
2013-10-23 02:18:49,809279 INFO EnetPeer.cpp dispatchIncomingCommands() line: 290 - command to dispatch not yet needed
2013-10-23 02:18:49,810149 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 230, returnCode: 0 {}
2013-10-23 02:18:49,810900 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 226
2013-10-23 02:18:50,112417 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 229, returnCode: 0 {}
2013-10-23 02:18:50,113866 INFO NetworkLogic.cpp connectReturn() line: 268 -
connected
2013-10-23 02:18:50,114394 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 230
2013-10-23 02:18:54,824511 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 226
1creating game "-517133284"
2013-10-23 02:18:56,329376 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 227, returnCode: 0 {230="95.211.58.243:5056", 255="-517133284"}
2013-10-23 02:18:57,333041 INFO LoadBalancingClient.cpp onStatusChanged() line: 505 - connected to gameserver
2013-10-23 02:18:57,640726 INFO LoadBalancingPeer.cpp opAuthenticate() line: 100 - OperationRequest - operationCode: 230 {224="YOUR_APP_ID", 220="1.0", 225="OS X"}
2013-10-23 02:18:58,042258 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 230, returnCode: 0 {}
2013-10-23 02:18:58,444147 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 227, returnCode: 0 {254=1, 248={250=[]}}
2013-10-23 02:18:58,445078 INFO NetworkLogic.cpp createRoomReturn() line: 288 -
2013-10-23 02:18:58,445820 INFO NetworkLogic.cpp createRoomReturn() line: 296 - localPlayerNr: 1
game room "-517133284" has been created
regularly sending dummy events now
2013-10-23 02:18:58,446940 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 255
2013-10-23 02:18:58,447675 INFO NetworkLogic.cpp joinRoomEventAction() line: 242 - OS X joined the game
player 1 OS X has joined the game
sssssssssssssssssssssssssssssssssssssssssssssssssssssss1ss
leaving game room
2013-10-23 02:19:04,472288 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 254, returnCode: 0 {}
2013-10-23 02:19:05,478600 INFO LoadBalancingClient.cpp onStatusChanged() line: 510 - connected to masterserver
2013-10-23 02:19:05,786180 INFO LoadBalancingPeer.cpp opAuthenticate() line: 100 - OperationRequest - operationCode: 230 {224="YOUR_APP_ID", 220="1.0", 225="OS X"}
2013-10-23 02:19:06,088374 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 226
2013-10-23 02:19:06,089500 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 230, returnCode: 0 {}
2013-10-23 02:19:06,390775 INFO LoadBalancingClient.cpp onOperationResponse() line: 306 - OperationResponse - operationCode: 229, returnCode: 0 {}
2013-10-23 02:19:06,391676 INFO NetworkLogic.cpp leaveRoomReturn() line: 336 -
game room has been successfully left
2013-10-23 02:19:06,392076 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 230
0terminating application
2013-10-23 02:19:11,103029 INFO LoadBalancingClient.cpp onEvent() line: 579 - EventData - code: 226
2013-10-23 02:19:11,404197 INFO NetworkLogic.cpp disconnectReturn() line: 281 -
disconnected
という感じで動くことを確認 ターミナルを二つ開いて
- Aが create game
- Bが random join game(gameが一つしかないので多分確定でAのいるところへ行く)
- Bが leave game
- Aが leave game
みたいなことが出来ることも確認できた
これで何かする予定もないので、これぐらいで今は満足満足