技術メモの管理

今後ちゃんと学んだことをメモに残しておこうと思い直し、メモを管理する仕組みを整理した。

保存場所

Dropbox/Documents/notes/以下。複数のPC間で簡単に共有したいのでDropboxで管理する。

メモを書く

まず以下のような関数を用意した。

note() {
    local note_path=$HOME/Dropbox/Documents/notes/$1
    if [ ! -e $note_path ]; then
        touch $note_path
    fi
    open $note_path
}

note <ファイル名>でメモを書き始めることができる。

メモはすべてmarkdown形式で、エディタはvimを使うことにした。vim-templateというプラグインを使うことで、notes/*.mdにマッチするファイルを以下のようなテンプレートで開くようにした。

---
title: <%= expand('%:t:r') %>
date: <%= strftime('%Y-%m-%d') %>
---

<+CURSOR+>

<%=%>で囲われたコードはVim Scriptとして評価されて展開され、<+CURSOR+>の位置をカーソルの初期位置としてファイルが開く。このような設定はvim-templateのヘルプにあるのでそちらを参照してほしい。

メモの検索

agとpecoを使う。agは高速なgrepということで、任意の文字列を含むファイルを検索する。

onote() {
    echo $(ag -l $1 $HOME/Dropbox/Documents/notes/) | peco | xargs open
}

template内で日付を必ず入れるようにしているので、日付で検索することもできるようになった。

ChefでRaspberry Piをセットアップする

仕事で複数台のRaspberry Piをセットアップすることになったので、Chefを使ってセットアップを自動化することにした。Chef、Vagrant、Serverspecなどいろいろな周辺ツールの全体像を整理したり、それらを使ったワークフローを体験できてよかったので、ブログとして残しておく。

また、セットアップに使ったChefのレポジトリはgithubにホストしてあるので参考にどうぞ。

https://github.com/naoty/chef-repo

今回、Chefで自動化したのは以下の通り。

  • apt-getの更新
  • gitのインストール
  • rbenvを使ってRuby 2.0.0-p247をインストール
  • nodebrewを使って最新安定版のnode.jsをインストール
  • Wiringpi(GPIOを簡単に操作するためのライブラリ)のインストール
  • mjpg-streamer(Webカメラを使ったストリーミングのためのライブラリ)のインストール

1. Vagrantで仮想環境を用意する

いきなりRaspberry PiにChefを使って環境構築を行うのは失敗したときにやり直すのが大変。なので、Raspberry Piに近い仮想環境を用意して、そこでChefを使ったセットアップを試行錯誤したい。そういうときに便利なのがVagrant。Vagrantを使えば簡単に仮想環境を作ったり壊したりできるので、失敗してもすぐにやり直せる。

今回、重要だったのがRaspberry Piに近い仮想環境を用意することだった。Vagrantにはboxという仕組みがあって、CentOSとかUbuntuとかいろんなOS、CPUに合わせたひな形がたくさん用意されている。通常はここにあるboxを使えばいいんだろうけど、Raspberry Piに近いboxがなかった。Raspberry Piに近いboxを探したところ、これがよさそうだったので使うことにした。

$ git clone https://github.com/nickhutchinson/raspberry-devbox raspberry_pi
$ cd rasbperry_pi
$ vagrant up

以上、これだけでRaspberry Piに近い仮想環境を用意することができた。

2. Chefのセットアップ

ここはいろんなところで解説されてる通りに行っただけ。

$ vagrant ssh-config --host vm-raspberry_pi >> ~/.ssh/config
$ knife solo init chef-repo
$ knife solo prepare vm-raspberry_pi

3. クックブックの作成とテスト

ここから環境構築の手順をコードとして記述していく。クックブックの書き方については「入門ChefSolo」やOpscodeの公式ドキュメントを参考にした。このときの注意点としては、Raspberry PiはRubyやnode.jsのインストールに非常に時間がかかるため、timeoutをとても長くする必要がある。数時間はかかると考えた方がいい。

書いたクックブックを実行する前にVagrantをサンドボックスモードにしておく。こうすると、失敗したときに実行した部分だけやり直すこと(ロールバック)ができる。サンドボックスモードにするためにはsaharaというVagrantのプラグインが必要なのでインストールしておく。

サンドボックスモードをオンにしてクックブックを実行したあと、本当に期待した通りに環境構築できたかどうかをServerspecを使ってテストする。Serverspecにはいくつかテストを実行する方法があるようだけど、今回はSSHでログインしてテストを実行する形式を採った。テストを通らなかった場合は、saharaを使ってロールバックしてやり直す。テストが通った場合は、saharaを使って変更を確定させる(コミット)。

これをサイクルさせながら、どんどんクックブックを追加していく。以上をコマンドで表すとこんな感じ。

$ knife cookbook create ruby -o site-cookbooks
$ vi site-cookbooks/ruby/recipes/default.rb
$ vi nodes/vm-raspberry_pi.json
$ vi spec/vm-raspberry_pi/ruby_spec.rb
$ vagrant sandbox on
$ knife solo cook vm-raspberry_pi
$ rspec
$ vagrant sandbox commit

4. Raspberry PiをChefで環境構築する

仮想環境での環境構築が完了したら、いよいよ本物のRaspberry Piにクックブックを適用する。そのためにはnodes以下に本物用の設定を追加するだけでいい。

$ vi nodes/raspberry_pi.json
$ knife solo cook raspberry_pi

RubyのWebSocketサーバー「pingpong」を作った

最近、「Working with TCP Sockets」って本を読んだ。Rubyでソケットと戯れつつ、7つくらいのWebサーバーのアーキテクチャを概観できるいい本だった。で、その中にイベント駆動モデルの実装とかノンブロッキングIOの実装について紹介されてて面白かったので、練習がてらWebSocketサーバーを作ることにした。

PingPong

f:id:naoty_k:20131011013521g:plain

https://github.com/naoty/pingpong

卓球ハウスっぽい名前にした。数日で作ったので、他のクライアントへpush通知を行うことしかできない。たぶん大きいデータも送れない気がする。

WebSocketサーバーの実装とは

まずはRFC 6455のサーバーに関する部分を読んだ。最低限必要な部分をRubyで実装していった。例えば、以下のコードはHandshake(websocket接続の確立)の際にサーバーがクライアントに返すレスポンスヘッダーを作っている。

def response_headers
  [
    ["Upgrade", "websocket"],
    ["Connection", "Upgrade"],
    ["Sec-WebSocket-Accept", signature]
  ]
end

def signature
  value = @header["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  hash = Digest::SHA1.digest(value)
  Base64.strict_encode64(hash)
end

ご覧のとおりハードコーディングがたくさん出てくる。RFCを読むと、このヘッダーにはこの値を入れなさいって書いてあることが多い。なので、それぞれの値の意味はわからないけどとりあえずRFCに従ってハードコーディングしている。signatureというメソッドはあるヘッダーの値をRFCで以下のように定められた形式で生成している。(余談だけど、ここでBase64.encode64を使って小1時間ハマった。これは改行コードを入れるためここでは使えない。)

A |Sec-WebSocket-Accept| header field. The value of this header field is constructed by concatenating /key/, defined above in step 4 in Section 4.2.2, with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this concatenated value to obtain a 20-byte value and base64-encoding (see Section 4 of [RFC4648]) this 20-byte hash.

イベント駆動モデルとノンブロッキングIO

push通知はイベント駆動モデルというアーキテクチャを使って実装した。イベント駆動モデルはマルチプロセスやマルチスレッドとは違ってシングルスレッドで多数のリクエストを並行処理する。具体的には、websocket接続の確立に成功したソケットを配列に入れておき、ループ内でそれらのソケットにread/writeしていく。このとき、read/writeがブロッキングしてしまうとすべての処理がそこで止まってしまうので、read/writeの前にselect(2)等を使ってread/write可能なソケットだけ選択してread/writeを行う。これがノンブロッキングIOだと思う(だよね…?)。

実際のコードは以下の通り。

def start
  @sockets = {}
  @message_queue = []

  loop do
    to_read = @sockets.values << @server
    to_write = @sockets.values
    readables, writables, _ = IO.select(to_read, to_write)

    readables.each do |socket|
      if socket == @server
        establish_connection
      else
        begin
          request = socket.read_nonblock(CHUNK_SIZE)
          message = Frame::Request.new(request).message
          # the message may be passed to a web application.
          @message_queue << Message.new(socket.fileno, message)
        rescue EOFError
          @sockets.delete(socket.fileno)
        end
      end
    end

    message = @message_queue.shift
    next if message.nil? || message.empty?

    writables.each do |socket|
      if socket.fileno != message.from
        data = Frame::Response.new(message.body).data
        socket.write_nonblock(data)
      end
    end
  end
end

感想

WebSocket、イベント駆動モデル、ノンブロッキングIO…という言葉はよく耳にしてきたけど理解したとは言えなかった。実際にWebSocketサーバーを書いてみると、コードに基づいて何が行われているのか正確に理解することができた。push通知も何やら凄そうな響きがするけど、実際に実装してみると特に難しいことはしていなかった。また、websocketの弱点と言われている、CPUヘビーな処理がなぜ弱点なのかも合点がいった。シングルスレッドで処理しているので、例えばレンダリングのような重い処理がひとつでも走ると、全体に悪影響が出るということだと理解した。

iOS用グラフ描画ライブラリを書き始めた

卓球ハウスに来てから楽しすぎてコード書いてないことに気づいたので、なんか書こうと思った。最近はセンサーのデータをどうにかiOSに転送したくていろいろ試しているのだけど、送られたデータを表示するときに何かとグラフを描画したくなる。

iOSでグラフを描画したいとき、まずCorePlotを試してみる。だけど、ドキュメントがあんまりないし、見た目がダサい。githubじゃないのもなんかなぁと思ってすぐにやめてしまう。で、CorePlotはやめてwebviewでJSのグラフ描画ライブラリを使うことにする。highchartsが便利なのでよく使う。でも、やっぱりいちいちHTMLとJSのファイルを用意しなくちゃいけないのがダルいとは思っていた。

そこでせっかくだしグラフ描画ライブラリをObj-Cで実装してみることにした。

naoty/NTChartView

f:id:naoty_k:20130806230903p:plain

まだぜんぜんできてない。かろうじて上のような折れ線グラフを表示できるレベル。負値すらうまく表示できない。もちろんテストもない。地味に頭を使う実装が多い気がする。

将来的には折れ線グラフだけじゃなくて円グラフと棒グラフも実装したい。けっこう時間かかりそうなので、ヒマなときにゆっくり実装したい。

半年経ちました・引っ越しました

2013年もあっという間に半年が経ちました。上半期の半分はiOSアプリやAndroidアプリを書いたり、プライベートでgemを書いたりしてました。上半期のもう半分は新しい会社でハードウェアの企画・開発に携わりました。この期間は自分のなかで失敗の連続で落ち込む日が多かったように思います。そしてけっこう太りました。技術面のみならずいろんなところで自分の至らなさを突きつけられました。プライベートでの時間の使い方が散漫だったのが最大の反省点です。もっとハードウェアという未知の領域の理解に時間をかけるべきでした。まだまだwebへの未練が捨てられず、ついついハードウェアよりweb的なものやアプリを作ったりvimいじりをしてしまうのがよくなかったです。ただ、昨年の大晦日にたてた目標、「ハードウェアを開発すること」は少しずつ実現していっているように思います。

そして、おととい引っ越しました。シェアハウスです。数年前に一度トライして失敗に終わりましたが、念願かなって実現できました。自分史上最高にいい家です。足が伸ばせるお風呂は初めてでした。住民は同年代のプログラマーで、僕よりはるかに優秀な人たちです。あと、彼らと親交のあるインターネッツ勢が来客されるので、僕は末席でじーっとしてます。

半年経った反省を基に、新たな環境で、なんとか2013年の目標を達成できるよう努力したいです。あと、かなりの出費で瀕死の状態がしばらく続きそうなので、アプリで一発あてたいです。よろしくお願いいたします。