Erlang: ログライブラリLager

前置き
サービスとか公開した後で継続的に機能追加、デバッグしていく事を考えるとログってかなり重要ですよね。。

外部の機器との通信内容とか、エラーが出た場合にそのときのデータの中身がどうなってたとか、ユーザーからのリクエストの内容がどうだったかとか、あとできればソースコードの何行目でどんな種類のエラー吐いたのか...とか。
あんまりログを吐かせるとパフォーマンスに影響しそうなので、その辺はバランスの問題ですが、パフォーマンスに問題が出ないレベルで、かつ見づらくならない程度であれば出来るだけログは吐かせたいです。

仕事で作ってるサービス(Ruby製)では、そんなに頻繁に通信要求が飛んで来るわけではないので外部機器とのやりとりは全てsyslogに吐き出させています。なので、なにか起こったらデータを解析してサーバ側で発生してる問題か、端末側で発生してる問題かの切り分けがすぐできます。さすがに秒間数百〜数千のリクエストがあるような所ではこれは現実的でないでしょうけど、うちぐらいならむしろログに出しておいて、その後の対応を速くする方が大事なので。


Lager
前置き長かったですが、ErlangのロガーのひとつであるLagerです。Erlangには標準でロガーがついてますが、正直わかりづらい...w ログわかりずらいって怖い...って思って調べたらわかりやすいのありましたw 表題のLagerです。
あのbashoによる開発のようです。

http://github.com/basho/lager

ちなみに既にV氏によるlager紹介記事がありますが、勉強を兼ねて自分でも書いてみます。読むのはあっちがいいと思いますw

標準ではコンソールへのアウトプットとlogディレクトリ以下へのファイル出力になるようですが、設定次第でSyslogへの送信もできるようです。AMQPにも対応してるとの事ですが、これは使った事無くてよくわかりません...

準備
ここではrebarを使っている事を前提にして書きます。
自分のプロジェクトで使うには、まずrebar.configを編集します。編集個所は2カ所。

  1. コンパイルオプションに {parse_transform, lager_transform} を追加する。
  2. depsにlagerを追加する。

の2つです。

1はrebar.config中のerl_optsに{parse_transform, lager_transform} を追加します。こんな感じ

{erl_opts, [warnings_as_errors,
            warn_export_all,
            warn_unused_import,
            warn_untyped_record,
            {parse_transform, lager_transform}
            ]}.

最後に追加してます。

2は他のアプリケーションを自分のプロジェクトに追加する時と同じです。

{deps, [

        {lager, 
         "1.*",
         {git, "git://github.com/basho/lager.git", 
          {branch, "master"}}},

        {meck, 
         "0.7.*",
         {git, "git://github.com/eproxus/meck.git", 
          {branch, "master"}}},

        {edown, 
         "0.3.*",
         {git, "git://github.com/esl/edown.git", 
          {branch, "master"}}}
       ]}.

先頭にlager追加しました。

あとは

$ ./rebar get-deps compile xref

すればおkです。

参考までに、rebar.config全体としてはこんな感じになりました。

{erl_opts, [warnings_as_errors,
            warn_export_all,
            warn_unused_import,
            warn_untyped_record,
            {parse_transform, lager_transform}
            ]}.

{xref_checks, [fail_on_warning, undefined_function_calls]}.

{cover_enabled, true}.

{sub_dirs, ["rel"]}.

{edoc_opts, [{doclet, edown_doclet}, 
             {dialyzer_specs, all}, 
             {report_missing_type, true},
             {report_type_mismatch, true}, 
             {pretty_print, erl_pp},
             {preprocess, true}]}.

{validate_app_modules, true}.

{deps, [

        {lager, 
         "1.*",
         {git, "git://github.com/basho/lager.git", 
          {branch, "master"}}},

        {meck, 
         "0.7.*",
         {git, "git://github.com/eproxus/meck.git", 
          {branch, "master"}}},

        {edown, 
         "0.3.*",
         {git, "git://github.com/esl/edown.git", 
          {branch, "master"}}}
       ]}.

使い方
使い方はいたって直感的です。最初にLagerアプリケーションを起動しておいて

application:start(lager).

あとはログを吐き出させたい場所で

lager:error("helo world!").

のように。あと動的に値を埋込みたい時は、いつもの感じで

lager:notice("helo ~p", ["world!"]).

のようにすれば標準でコンソール出力と、アプリケーションディレクトリ直下のlogディレクトリ内にerror.logとconsole.logが作られて書き込まれます。

一応注意が必要なのは、コンソールで直接 lager:error("hoge") とかやってもundefined functionて怒られます。
コードの中に書いてないと動きませんのでご注意を。

ちなみにV氏のサンプルだと、いきなりapplication:start(lager)で動きましたが、僕の手元のプロジェクトでは、そのまえに

application:start(compiler).
application:start(syntax_tools).

しないと怒られました。なにが違うんだろう...?

ログレベルには

  • debug
  • info
  • notice
  • warning
  • error
  • critical
  • alert
  • emergency

があるようです。lagerモジュールにこのログレベルを関数名として呼び出せばいいです。lager:notice/2とか。

ただ、lager.erlのソース見てみると、これらの関数は定義もexportもされてないんですよねー。-export_typeってのがなんかやってるようですが、まだわかってません...わからんことだらけ。

設定変更(出力先ファイルおよびログレベル等)
標準でログはファイル(アプリケーションのルートディレクトリのlogディレクトリ以下)へ書き出されますが、書き出しレベルを変更したい場合はapplication:start(lager)する前に、一旦application:load(lager)しておいて、環境設定を保存してからapplication:start(lager)します。

例えばこんな感じでアプリケーションの.app.srcファイルに書いておいて

{application, lager_sample,
 [
  {description, ""},
  {vsn, "0.1"},
  {registered, []},
  {applications, [
                  kernel,
                  stdlib
                 ]},
  {mod, { lager_sample_app, []}},

  {env, [
    {lager, [
      {handlers, [
        {lager_console_backend, debug},
        {lager_file_backend, [
          {"log/error.log", error, 10485760, "$D05", 5},
          {"log/console.log", debug, 10485760, "$D05", 5},
          {"/var/log/lager_sample/error.log", error, 10485760, "$D05", 5},
          {"/var/log/lager_sample/console.log", debug, 10485760, "$D05", 5}
        ]}
      ]}
    ]}

  ]}

 ]}.

起動時に

    application:start(lager_sample),
    {ok, [{handlers, LConfig}]} = application:get_env(lager_sample, lager),    

    application:load(lager),
    application:set_env(lager, handlers, LConfig),
    application:start(lager).

とすると上記の設定で起動出来ます。
なお、この設定では絶対パスを指定して/var/log/lager_sampleディレクトリ以下にもログを書き出しているので事前にディレクトリを作って適切なパーミッションにしておく必要があります。

また、この設定だとコンソールと log/console.logおよび/var/log/lager_sample/console.logにはdebugレベル以上(つまり全て)のログを書き出し、log/error.logおよび/var/log/lager_sample/error.logにはerrorレベル以上のログを書き出します。

また、

{"error.log", error, 10485760, "$D05", 5},

の中で"$D05"と書いてますが、これは毎日朝5時にログのローテートを行うという意味です。
あと10485760はローテートするファイルサイズ、5は最大でで5つまでファイルを残しておくという意味です。

{ファイル名, ログレベル, ファイルサイズ, ローテートのスケジュール, 保持するファイル数},

となります。

あとは通常通り、lager:debug("hoge") などとすれば設定に応じてログが吐き出されます。

サンプル
githubに、今回の学習であれこれ弄ったサンプルgithubに置いておきます。
使い方はプロジェクトのREAMEに書いてあります。

追記: Syslogとの連携
最初のほうで書いたように、LagerはSyslogとの連携もサポートしています。厳密にはSyslogとの連携をする為の部分はlager_syslogという別のプロジェクトに切り分けてあるようですが、このlager_syslogを自分のアプリケーションに組み込んで、あとはちゃちゃっと設定するだけで、今まで通りlager:error("hogehoge") とかすると、syslogに日時情報やノード情報と共に"hogehoge"と出力されます。
ただ、lager_syslogが依存しているerlang_syslogがLinuxしかサポートしてないっぽいです。手元ののMacOSX環境では動作しませんでした。ですので試す場合はLinuxで試してみてください。

ファイルとコンソールに書き出していた場合と変更する部分は

  1. rebar.configのdepsにlager_syslogを追加
  2. lagerのenv設定にsyslogを使う設定を追加

の2つです。

以下がlager_syslogを組み込むようにしたrebar.config全体です。

{erl_opts, [warnings_as_errors,
            warn_export_all,
            warn_unused_import,
            warn_untyped_record,
            {parse_transform, lager_transform}
            ]}.

{xref_checks, [fail_on_warning, undefined_function_calls]}.

{cover_enabled, true}.

{sub_dirs, ["rel"]}.

{edoc_opts, [{doclet, edown_doclet}, 
             {dialyzer_specs, all}, 
             {report_missing_type, true},
             {report_type_mismatch, true}, 
             {pretty_print, erl_pp},
             {preprocess, true}]}.

{validate_app_modules, true}.

{deps, [

        {lager, 
         "1.*",
         {git, "git://github.com/basho/lager.git", 
          {branch, "master"}}},

        {lager_syslog, 
         "0.9.*",
         % {git, "git://github.com/basho/lager_syslog.git", 
         {git, "git://github.com/hiroeorz/lager_syslog.git", 
          {branch, "master"}}},

        {meck, 
         "0.7.*",
         {git, "git://github.com/eproxus/meck.git", 
          {branch, "master"}}},

        {edown, 
         "0.3.*",
         {git, "git://github.com/esl/edown.git", 
          {branch, "master"}}}
       ]}.

変わったのはdepsのlagerの下にlager_syslogを追加した事だけです。
bashoのオリジナルのlager_syslogはlagerのバージョン1.0.0を要求しているのに対してlagerは1.2.0に上がっており、かつタグもなかったのでlager_syslogをフォークしてバージョン番号だけ直したものを使いました。コメントアウトしてあるほうがオリジナルなのでバージョン番号が一致していたらそちらを使われた方がいいです。まあ今回はデモという事で。

それからlagerのenv設定の所で、以下の設定を追記します。

{lager_syslog_backend, ["lager_sample", local0, debug]}

"lager_sample"は識別子、local0はsyslogの種別、debugはログレベルです。

ファイルへの出力やコンソール出力と併用するとこんな感じになりますね。

    {lager, [
      {handlers, [
        {lager_console_backend, debug},
        {lager_file_backend, [
          {"log/error.log", error, 10485760, "$D05", 5},
          {"log/console.log", debug, 10485760, "$D05", 5},
          {"/var/log/lager_sample/error.log", error, 10485760, "$D05", 5},
          {"/var/log/lager_sample/console.log", debug, 10485760, "$D05", 5}
        ]},
        {lager_syslog_backend, ["lager_sample", local0, debug]}
      ]}
    ]}

最後にsyslogの設定を追加しています。

上でアプリの.app.srcに設定してlager起動前に設定を変更する方法を使うとこんな感じです。

アプリの.app.srcファイル

{application, lager_sample,
 [
  {description, ""},
  {vsn, "0.1"},
  {registered, []},
  {applications, [
                  kernel,
                  stdlib
                 ]},
  {mod, { lager_sample_app, []}},

  {env, [
    {lager, [
      {handlers, [
        {lager_console_backend, debug},
        {lager_file_backend, [
          {"log/error.log", error, 10485760, "$D05", 5},
          {"log/console.log", debug, 10485760, "$D05", 5},
          {"/var/log/lager_sample/error.log", error, 10485760, "$D05", 5},
          {"/var/log/lager_sample/console.log", debug, 10485760, "$D05", 5}
        ]},
        {lager_syslog_backend, ["lager_sample", local0, debug]}
      ]}
    ]}

  ]}

 ]}.

起動時に以下のようにlagerの設定を入れ替える

    application:start(lager_sample),
    {ok, [{handlers, LConfig}]} = application:get_env(lager_sample, lager),    

    application:load(lager),
    application:set_env(lager, handlers, LConfig),
    application:start(lager).

これで後は通常通り、

lager:debug("this id debug mesage!").

のようにコード中に書いておけばsyslogに出力されます。

Syslogへログを吐き出すサンプルは上のサンプルの"syslogブランチ"にあります。

Erlang: 依存関係にあるアプリケーションのヘッダファイルをインクルードするには

表題の件のメモ。

普通に自分のincludeディレクトリにある場合は

-include("hoge,hrl").

とかしますが、例えば依存関係にあるアプリケーションのヘッダファイルをインクルードする場合は

-include_lib("AppName/include/hoge.hrl").

とかでいいみたいです。
AppNameのとこは対象のアプリケーション名。

相対パスでも読み込めますが、それだと対象のアプリケーションとしてコンパイルする場合と依存関係の一部としてコンパイルする場合とで(表現むずかし)パスが変わって来るので上記の書き方がいいのかなと。

rebar generateでErlangアプリをパッケージ化したときのメモ

なんの言語で作ったにせよ、サーバアプリケーションであれば最終的に本番サーバで稼働させて初めて意味を成します。
いわゆるデプロイと言われる作業ですが、これが意外に難物で、アプリケーションが出来上がってからサーバをセットアップして稼働させようとすると、特に依存関係周りで色々とひっかかることも少なくないです。

で、Erlangを見てみると、さすがに長年本番環境で使用されてきたというか、その為に作られた言語と実行環境だけあってデプロイ周りがキッチリしています。

Erlang自体にもともと製品リリースの為の仕組みがあるようですが、Erlangをバリバリ仕事で使っているというbashoの方々による成果物rebarを使うと更に楽ができます。

"rebar generate"でパッケージ化されたアプリケーションにはbeamファイルだけでなく、stdlibなどの基本ライブラリや更にはErlang自体も含まれています。これってJavaとかでは普通の事なのかもしれませんがRubyではここまでした事なかったのでちょっと驚きました。

なお、僕が実際にrebarでアプリケーションをリリースパッケージ化してみた際にはVoluntus氏から色々とアドバイスを頂き、設定サンプルまで作って頂きました。この場を借りてお礼を申し上げますo┐ペコリ

リリースディレクトリ構成の自動生成
前置きはこれくらいにして早速自分のアプリをパッケージ化してみます。
参考にしたのはここ: Release handling Introducing reltool.config

ここでは既にrebarによるアプリケーションの生成は終わっているものとします。
(アプリケーション新規生成のコマンドは "rebar create-app appid=hogeapplication" )

また、使用するrebarコマンドはアプリケーションディレクトリのルートディレクトリに置いてあるものとします。
rebarはここ

パッケージ化するにはまずrelディレクトリをアプリケーションのルートディレクトリに手動で作ります。

$ cd hogeapplication
$ mkdir rel

作ったディレクトリに移動してrebar create-nodeで設定ファイル等のひな形を作ります。

$ cd rel
$ ../rebar create-node nodeid=hogeapplication

これでrelディレクトリ内にfilesディレクトリとreltool.configファイルができ上がります。

$ ls
files/           reltool.config

設定ファイルの編集
続いて自動生成されたreltool.configを自分の環境に合わせて編集します。
ここに僕が作ってみたサンプルを置いておきます。上記の流れでアプリケーション名はhogeapplicationとして見てください。
Erlangリリースパッケージ生成ツール用設定ファイルのサンプル

最初、全ての基本ライブラリや直接依存していないライブラリも含める事に気づかずハマりましたがVoluntus氏のアドバイスにより理解しました。
あと、rebarのバージョンが古くて、ここでもハマりましたw なお、ここで使っているrebarのバージョンは以下の通りです。

$ /rebar --version
rebar version: 2 date: 20120516_113044 vcs: git 2e36109

さて、設定ファイルが出来上がったらあとは generateを実行するのみです。

 $ cd rel
 $ ../rebar generate
==> rel (generate)
$

なにもエラーが出ずにプロンプトが戻ってきたら無事成功です。
relディレクトリ内にノード名のディレクトリ(今回はhogeapplication)のディレクトリが生成され、そのなかにbinディレクトリがあるはずです。

アプリケーションの制御
あとはこのbinディレクトリ内のコマンドにstart, stop等のオプションを付けて実行する事で制御出来ます。

起動は

$ ./hogeapplication/bin/hogeapplication start

停止

$ ./hogeapplication/bin/hogeapplication stop

restartやrebootもありますが、自分の環境では"ok"と表示されるものの、アプリケーションは起動していませんでした。

あと、rebar startで起動するとコンソールは戻ってきてアプリケーションはデーモンとして起動されますが、稼働中のアプリケーションのコンソールに入りたい場合もあります。その場合は

$ ./hogeapplication/bin/hogeapplication attach

とすることで稼働させたままコンソールに入る事ができます。

ちなみに出来上がったhogeapplicationディレクトリの中をのぞいてみると、erlコマンドや基本ライブラリ、依存ライブラリのbeamファイルが含まれています。
従って基本的にはこのパッケージを持っていけばよその環境でも動くはずです(クロスコンパイルについては調べてませんが...)
Erlang本体を含めて全ての依存関係をパッケージしてしまうのでデプロイ時のライブラリや実行環境のバージョンなどの依存関係に起因するトラブルには格段に強そうだと思いました。


さて、ちょっと長くなったのでとりあえず今回はここまでで、このパッケージ使った無停止アップグレードしてみたメモは次回に。

Erlang R15B01 をDebianにインストールした時のメモ

入れたてのDebianErlangをインストールしました。
手順は以下。

依存関係パッケージ入れる(全部は必要ないかも)

$ sudo aptitude install make gcc g++ tcl8.5
$ sudo aptitude install openjdk-6-jdk openjdk-6-jre openjdk-6-jre-lib openjdk-6-source
$ sudo aptitude install unixodbc unixodbc-dev unixodbc-bin

最新版のソースコードを落とす

$ wget http://www.erlang.org/download/otp_src_R15B01.tar.gz

解凍

$ tar xvzf otp_src_R15B01.tar.gz

ディレクトリに入ってconfigure, make, make install

$ cd otp_src_R15B01
$ ./configure --enable-smp-support --enable-kernel-poll
$ make
$ sudo make install

動作確認

$ erl
Erlang R15B01 (erts-5.9.1) [source] [async-threads:0] [kernel-poll:false]

Eshell V5.9.1  (abort with ^G)
1> 
    • enable-smp-supportにしてみたものの、よく考えたら仮想環境でCPU一個しか無かった Orz

Beagleboard-xmでAndroid動かしたときのメモ

購入品
記念撮影

詳細な購入品リスト(背景が灰色のものは検討したものの購入していないものです)。
最終的な金額は液晶も含めて4万2千円程度でした。

参考にさせて頂いた書籍
そもそも組み込みのAndroidに興味を持ったのは「組み込みAndroid」という書籍を読んだからです。beagleboard-xmへのAndroidポーティングに関しては若干内容が古いですが、大変参考になります。

基礎から学ぶ 組み込みAndroid

基礎から学ぶ 組み込みAndroid

参考にさせて頂いたサイト
TI-Android-GingerBread-2.3.4-DevKit-2.1 DeveloperGuide
BeagleBoard-xM に Android Gingerbread をポーティング

なお、上の写真に移っているタッチパネルディスプレイ(HM-TL7T)でタッチパネル機能を使えるようにするためにはLinuxカーネルソースに含まれるファイルを若干修正する必要がありました。
これには「LinuxでHM-TL10Tのタッチパネルを認識させる」を参考にさせていただきました。
デバイスドライバのことなどまったく分からないのでこのようなサイトがあって大変助かりました。

事前の準備
GingerBreadをコンパイルするには64bitのUbuntu Linuxが必要です。私は手元のMacBookProにUbuntu用のパーティションを作ってダブルブート環境にしました。
Macで使えるUbuntu Linux 64bit版はこちらにあります。
Ubuntu 11.10 (Oneiric Ocelot)

既にMacをインストールしている場合もディスクユーティリティで新たにUbuntu用のパーティションを作成可能です。ただしその場合、BootCampでWindowsデュアルブートする環境にしていたらWindowsが起動できなくなる可能性が高いですので必ずバックアップは取っておいてください。私は起動しなくなりましたw

MacBookProにUbuntuをインストールする手順については以下のサイトを参考にさせていただきました。
defiantの日記

Ubuntuをインストールできたら端末から必要なパッケージをインストールします。

$ sudo aptitude install git-core gnupg flex bison gperf build-essential zip curl sun-java6-jdk zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev ia32-libs x11proto-core-dev libx11-dev lib32readline5-dev lib32z-dev uboot-mkimage

gcc関連をインストール

$ sudo apt-get install gcc-4.5
$ sudo apt-get install gcc-4.5-multilib
$ sudo apt-get install g++-4.5
$ sudo apt-get install g++-4.5-multilib
$ export CC=gcc-4.5
$ export CXX=g++-4.5

ソースコードその他をダウンロードする
必要なソースコードその他はTexus Instruments のダウンロードサイトにおいてあります。

Texus InstrumentsのサイトにはGingerBreadに関して2.3と2.3.4の2種類がありますが、最新の [beagleboard-xm RevC] 上でGingerBreadを動かす場合は必ず2.3.4の方を使用してください。2.3ではマウスやネットワークデバイスの認識に関してRevCには対応していないらしく、そのままではマウスもネットワークも認識しません。

なお、ここでのこれ以降の作業は"~/src/beagle-android/GingerBread_2_3_4"というディレクトリを作って、そこにソースと開発キットをダウンロードして行うことを前提としています。

$ mkdir -p ~/src/beagle-android/GingerBread_2_3_4
$ cd ~/src/beagle-android/GingerBread_2_3_4
$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_4_DevKit_2_1/exports/TI_Android_GingerBread_2_3_4Sources.tar.gz

同じくTexus Instrumentのサイトから開発キットをダウンロードする。

$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_4_DevKit_2_1/exports/TI_Android_GingerBread_2_3_4_DevKit_2_1.tar.gz

あとはbeagleboard-xm向けのビルド済みパッケージも落としておくといいと思います。私の環境では自前でコンパイルしたMLOとu-boot.binが動作しなかったので、ビルド済みパッケージに含まれていたものを使わせてもらいました。
↑ビルドできました

$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_4_DevKit_2_1/exports/beagleboard-xm.tar.gz

ダウンロードには数時間かかります。寝る前に上記2つを別の端末で実行しておいて寝て待つのがいいと思われます。

ビルドの準備
ダウンロードが終わったらTI-Android-GingerBread-2.3-4DevKit-2.1 DeveloperGuideに従ってAndroidをビルドします。

まずは解凍

$ tar xvzf TI_Android_GingerBread_2_3_4Sources.tar.gz
$ tar xvzf TI_Android_GingerBread_2_3_4_DevKit_2_1.tar.gz 

ソースディレクトリに移動してレポジトリをセットアップします。repoについては正直よく分かってないところがあるのですが、内部でgitを操作しているらしく、下記を実行するとディレクトリにソースコードが配置されます。

$ cd TI_Android_GingerBread_2_3_4Sources/
$ ./.repo/repo/repo sync --local-only

ツールのあるディレクトリにパスを通しておきます。

export PATH=~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin:$PATH

Beagleboard-XMにおけるAndroidの起動プロセス
予備知識として、beagleboard-xm上でのブートプロセスは以下のようになるようです。
(起動時の出力を見ての憶測に基づいたものであり、ソースを見たわけではありません)

  1. beagleboard-xm電源投入
  2. (beagleboard-xm内のブートローダ起動?)
  3. microSDカードのbootパーティションの最初にあるブートローダ"MLO"を起動
  4. microSDカードのbootパーティションにある"u-boot.bin"を起動
  5. microSDカードのbootパーティションにある"boot.scr"を実行
  6. microSDカードのbootパーティションにある"uImage"を起動
  7. Linuxカーネルが展開されて起動プロセスに入る
  8. Android起動

ここからの過程では上記のMLO, u-boot.bin, boot.scr, uImage、そしてAndroidをビルドしていきます。

ブートローダ(MLO)のビルド
MLOのビルドはソースディレクトリ内のx-loaderディレクトリに移動して行います。

$ cd x-loader
$ make CROSS_COMPILE=arm-eabi- distclean
$ make CROSS_COMPILE=arm-eabi- omapbeagle_config
$ make CROSS_COMPILE=arm-eabi-

できあがったx-load.binからデベロッパーキットに含まれるsignGPを使ってMLOを作ります。

$ ../../TI_Android_GingerBread_2_3_4_DevKit_1_0/Tools/signGP/signGP ./x-load.bin

x-load.bin.iftというファイルが作られるので、これをMLOにリネームして、実行権を付加しておきます。

$ mv x-load.bin.ift MLO
$ chmod 755 MLO

u-boot.binのビルド
u-boot.binはソースディレクトリ以下のu-bootディレクトリで行います。

$ cd u-boot
$ make CROSS_COMPILE=arm-eabi- distclean
$ make CROSS_COMPILE=arm-eabi- omap3_beagle_config
$ make CROSS_COMPILE=arm-eabi-

これでu-boot.binが作られます。
ここでカレントディレクトリにあるtoolsディレクトリ内にmkimageというファイルが作られています。この後の工程で使用しますので"/usr/bin"または"/usr/local/bin"、"~/bin"など、パスの通っているディレクトリにコピーしておいてください。
私の環境では~/binにパスが通ってますのでそこにコピーしておきました。

$ cp tools/mkimage ~/bin/

boot.scrの作成
boot.scrの作成はデベロッパーキットに移動して行います。

$ cd ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4_DevKit_2_1/Tools/mk-bootscr/
$ ./mkbootscr

これで一旦boot.scrが作られますが、内容をカスタマイズしたいのでboot.scriptというファイルを以下の内容で、同じディレクトリ内に作ります。

mmc init
fatload mmc 1 80200000 uImage
setenv bootargs 'console=ttyO2,115200n8 androidboot.console=ttyO2 root=/dev/mmcblk0p2 rw rootfstype=ext3 rootdelay=1 init=/init ip=dhcp omap_vout.vid1_static_vrfb_alloc=y vram=12M omapfb.vram=0:8M,1:4M,2:4M omapfb.mode=dvi:800x600@60 omapdss.def_disp=dvi'
bootm 0x80200000

そしてこれを元に以下のコマンドでboot.scrを作成します。

$ sudo ./mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "BeagleBoard" -d boot.script boot.scr

Linuxカーネルのビルド
Linuxカーネルコンパイルを行います。Linuxカーネルは上で落としたAndroidソースコードのkernelディレクトリ内にあります。

$ cd kernel
$ export CC=gcc-4.5
$ export CXX=g++-4.5
$ make ARCH=arm CROSS_COMPILE=arm-eabi- distclean
$ make ARCH=arm CROSS_COMPILE=arm-eabi- omap3_beagle_android_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-eabi- uImage

Androidのビルド
つづいてAndroidコンパイルします。
私の環境ではmakeを実行した際にエラーが発生しましたので実際のコンパイルに入る前にMakefileを修正しておきます。

$ vim build/core/combo/HOST_linux-x86.mk

このファイル中の"HOST_GLOBAL_CFLAGS..." から始まる行をコメントアウトします。

HOST_GLOBAL_CFLAGS += -D_FORTIFY_SOURCE=0
  ↑
# HOST_GLOBAL_CFLAGS += -D_FORTIFY_SOURCE=0

それからAndroidコンパイルにはJava6が必要ですが、64bit版Ubuntuのaptで入る64bit版Javaではコンパイル途中でエラーが発生します。

そこで、Sunのホームページから32bit版のJava6を落として、update-alternativesコマンドで32bit版Javaを使うように設定した上でコンパイルを開始します。
Sunのホームページから32bit版Javajdk-6u30-linux-i586.bin)をダウンロードします。
/usr/localにコピーして解凍し、システムがそちらのJavaを使うように設定します。

$ sudo cp jdk-6u30-linux-i586.bin /usr/local/
$ cd /usr/local
$ sudo ./jdk-6u30-linux-i586.bin
$ sudo update-alternatives --install "/usr/bin/java" "java" "/usr/local/jdk1.6.0_30/bin/java" 1
$ sudo update-alternatives --config java

ここで表示された設定画面で/usr/local/jdk1.6.0_30/bin/java の番号を選択する。

alternative java (/usr/bin/java を提供) には 4 個の選択肢があります。

  選択肢    パス                                    優先度  状態
------------------------------------------------------------
 * 0            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      自動モード
  1            /usr/lib/jvm/java-1.5.0-sun/jre/bin/java   53        手動モード
  2            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      手動モード
  3            /usr/lib/jvm/java-6-sun/jre/bin/java       63        手動モード
  4            /usr/local/jdk1.6.0_30/bin/java            1         手動モード

現在の選択 [*] を保持するには Enter、さもなければ選択肢の番号のキーを押してください:4

設定したら再度上記コマンドを実行して32bit版Javaが選択されていることを確認してください。

ここまでできたら、いよいよAndroidコンパイルします。
ここで-jオプションで並列実行数を指定します。-j5の場合は5つのビルドプロセスが同時に実行されます。
「組み込みAndoid」によると通常は(コア数+1)を指定するとのことなのでMacBookPro 2コア(ハイパースレッディングで4コア)では-j5を指定します。

$ export CC=gcc-4.5
$ export CXX=g++-4.5
$ make TARGET_PRODUCT=beagleboard OMAPES=5.x -j5

MacBookPro 2コア(ハイパースレッディングで4コア)では35分程度で完了しました。

microSDカードへのポーティング
microSDカードへインストールします。
まずはホームディレクトリ以下に”images”ディレクトリを作って必要なものを全てそこに集めます。

$ mkdir ~/images
$ cd ~/images

uImageを持ってきます。

$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/kernel/arch/arm/boot/uImage ~/images/

Androidディレクトリツリーを作って~/images以下にコピーします。

$ cd ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/out/target/product/beagleboard/
$ mkdir android_rootfs
$ cp -r root/* android_rootfs/
$ cp -r system android_rootfs/
$ sudo ../../../../build/tools/mktarball.sh ../../../host/linux-x86/bin/fs_get_stats android_rootfs . rootfs rootfs.tar.bz2
$ cp rootfs.tar.bz2 ~/images/

MLOとu-boot.bin、そしてboot.scrをコピーします。

$ cd ~/images
$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/x-loader/MLO ./
$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/u-boot/u-boot.bin ./
$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4_DevKit_2_1/Tools/mk-bootscr/boot.scr ./

また、プレビルドパッケージからmkmmc-android.shとMedia_Clipsを持ってきます。

$ cp ~/src/android-prebuild/android-gingerbread/beagleboard-xm/mkmmc-android.sh ./
$ cp -r  ../TI_Android_GingerBread_2_3_DevKit_1_0/Prebuilt_Images/beagleboard-xm/Media_Clips ~/images/

この時点で~/imagesディレクトリ以下には以下のファイルがあることを確認してください。

  • MLO
  • boot.scr
  • rootfs.tar.bz2
  • uImage
  • Media_Clips
  • mkmmc-android.sh
  • u-boot.bin

microSDカードにインストールします。ここで使うmkmmc-android.shは日本語環境では正常に動作しないのでLANG=Cをセットしておきます。
また、コマンド中の/dev/sdcは環境によって異なります。syslogやdfコマンドでデバイスを確認してください。

$ export LANG=C
$ sudo ./mkmmc-android.sh /dev/sdc MLO u-boot.bin uImage boot.scr rootfs.tar.bz2 Media_Clips

ここではmkmmc-android.shを使ってmicroSDカードにファイルを配置しますが、中でやっている事は

  1. microSDカードにboot, rootfs, dataの3つのパーティションを切る。
  2. パーティションをフォーマット
  3. MLO, u-boot.bin boot.scr uImageをbootパーティションに配置
  4. rootfs.tar.bz2をrootfsパーティションに展開
  5. MediaClipsをdataパーティションにコピー

となります。これを手動で行う事ももちろん可能ですが、その場合はbootパーティションフォーマット後のファイルのコピー順が重要となるそうです。最初は必ずMLO、続いてu-boot.binをコピーしないと正常に起動しません。それはbeagleboard-xmがbootパーティションの最初のセクタからMLOを探すかららしいです。
また、後からファイルを更新する時は必ず上書きコピーするように「組み込みAndroid」に書いてありました。一旦削除してから保存し直すとうまく起動出来ないようです。


Android起動

あとはこれをbeagleboard-xmに差して電源を入れますが、起動ログを確認するためにRS232C-USB変換ケーブルでbeagleboard-xmとパソコンをつなぎます。
そしてscreenコマンドを入れて起動ログを見られるようにします。

$ sudo apt-get install screen
$ screen /dev/ttyUSB0 115200

上記を実行すると端末上にbeagleboardのログが流れる準備が整いました(beagleboardを起動するまでは何も表示されません)

あとはbeagleboard-xmを起動します。初回起動時はかなり待たされます(10〜15分)ので、ドキドキしながら気長に待ちましょうw

キタ━━━ヽ(∀゚ )人(゚∀゚)人( ゚∀)ノ━━━!!

「LinuxでHM-TL10Tのタッチパネルを認識させる」に従ってデバイスドライバの修正とカーネルビルドの設定を行ったところ、無事タッチパネルも使用できました。

↑嬉々としてタッチするの図。ただしこのタッチパネルスクリーンは爪の先でタッチしないと反応が悪いですw
起動後暫くは内部でロードを行っているらしく動きがトロいのですが、それが落ちつくとわりとサクサクと操作できます。

Emacsで文字列中の改行を全部消す

ログにプログラム間でのやり取りの際の電文を書き出させてるんだけど、これを端末から直接コピーすると改行が入っちゃう。
けど電文は結構長いのもあるので一個一個消すなど大変なので一発で消す方法のメモ。

M-x replace-string 置換前 C-q C-j 置換後 (何も入力しない)
Enter

置換前

000001111222233
122223333
111111111111111111111111111111111
333333333333333333333333333333333

置換後

000001111222233122223333111111111111111111111111111111111333333333333333333333333333333333

Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その5)

さて、なんかMochiWebをさらっと触ってみたシリーズも第5弾となりました。ちょっとこのタイトルではじめた事を後悔しつつありますが、気を取り直していってみましょ。

過去記事はこちら
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その2)
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その3)
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その4)

今回はSNSなどでは必須となるログイン機構と、それに伴うセッションの実装です。
僕自身、ログインに関しては今までほとんどをフレームワーク任せだったのでちと緊張しますが頑張ってみます←
ご指摘等は歓迎しますので泣かない程度にやってやってください。

セッションの基本的な動作
セッションはサーバ側に各クライアント毎のデータを保存しておいてページ感で情報を共有する為の仕組みです。
そして通常は、どのデータがどのクライアントのものかを見分ける為にクッキーを使います。クッキーに保存したキーを元にして、そのクライアントのデータを探し出すわけです。
従って一般的な流れとしては

  1. ログイン
  2. セッションにログイン情報を保存
  3. クライアントのクッキーにに、セッションIDを保存
  4. 次回からのアクセスでは都度、クライアントからセッションIDが送られて来るので、それをもとにセッションデータを参照、更新する

という流れとなります。

実装
上記を実装したコードが以下になります。
今回は動作原理を理解する為にmemcachedのようなキーバリューストアは使いません。てか、データベースすら使わずハードコーディングしています(笑) まあ何事も一歩一歩という事でw

-module(session_action).

-include_lib("eunit/include/eunit.hrl").

-export([start/1, stop/0]).


start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

%%%===================================================================
%%% Reqest Dispatcher
%%%===================================================================

dispatch(Req) ->
    Path = Req:get(path),
    Params = Req:parse_qs(),

    case string:tokens(Path, "/") of
        [Controller, Action | UrlParams] ->
            handle(Controller, Action, UrlParams, Params, Req);
        [Controller] ->
            handle(Controller, "index", [], Params, Req);
        [] ->
            handle("top", "index", [], [], Req)
    end.

%%%===================================================================
%%% Reqest Handlers
%%%===================================================================

%% Topページ
handle("top", "index", _Path, _Params, Req) ->
    Req:ok({"text/html", "Welcome!"});

%% ユーザページ
handle("user", "welcome", [Name], _Params, Req) ->
    Req:ok({"text/html", subst("Welcom(in url) ~s!", [Name])});

%% ユーザ名とパスワードで認証する。成功したらセッションIDをクッキーに保存する。
handle("auth", "login", [], Params, Req) ->
    Username = proplists:get_value("username", Params),
    Password = proplists:get_value("password", Params),

    case authenticate(Username, Password) of
        {ok, UserId} ->
            SessionId  = get_session_keys(Username, Password, UserId),
            create_new_session(SessionId, UserId),
            Header1 = set_cookie("session_id", SessionId),
            Header2 = set_cookie("user_id", UserId),
            Req:ok({"text/html", [Header1, Header2], <<"ok! logged in :-)">>});
        {error, login_failure} ->
            Req:respond({403, [{"Content-Type", "text/html"}], 
                         <<"UnAuthenticated :-<">>})
    end;

%% ログアウト処理
handle("auth", "logout", [], _Params, Req) ->
    Header1 = set_cookie("session_id", ""),
    Header2 = set_cookie("user_id", ""),
    Req:ok({"text/html", [Header1, Header2], <<"logged out.">>});

%% ログイン後でないと見る事の出来ないコンテンツ
handle("secure_space", "index", [], _Params, Req) ->
    case check_loggedin(Req) of
        ok ->
            Name = get_session_value("name", Req),
            Req:ok({"text/html", subst("There is Secure Space (~s)", [Name])});
        {error, unauthenticated} ->
            Req:respond({403, [{"Content-Type", "text/html"}], 
                         <<"UnAuthenticated :-<">>})
    end;

%% 404 Not Found
handle(_, _, _, _, Req) ->
    Req:not_found().

%%%===================================================================
%%% Internal Functions
%%%===================================================================

set_cookie(Key, Value) ->
    mochiweb_cookies:cookie(Key, Value, [{path, "/"}]).


%% 既にログイン済みかどうか確認する。
check_loggedin(Req) ->
    case get_session_value("loggedin", Req) of
        true -> ok;
        _ -> {error, unauthenticated}
    end.

%% ユーザ認証を行う。
authenticate(Username, Password) ->
    case [Username, Password] of
        ["shin", "mypassword1"] -> {ok, "1"};
        ["tuna", "mypassword2"] -> {ok, "2"};
        _                       -> {error, login_failure}
    end.

%% ユーザ情報からセッションIDを生成する。
get_session_keys(Username, _Password, _UserId) ->
    case Username of
        "shin" -> "aaabbbcccddd";
        "tuna" -> "eeefffggghhh"
    end.     

%% セッションデータから与えられたキーに対応する値を返す。
get_session_value(Key, Req) ->
    case Req:get_cookie_value("session_id") of
        undefined ->  undefined;
        SessionId ->
            SessionData = get_session_data(SessionId),
            UserId = Req:get_cookie_value("user_id"),
            case proplists:get_value("id", SessionData) of
                UserId -> proplists:get_value(Key, SessionData);
                _ -> undefined
            end
    end.

%% ログイン時にセッションにユーザデータ等を保存する関数。今回はなにもしない。
create_new_session(_SessionId, _UserId) ->
    ok.

%% 与えられたセッションキーに対応するセッション情報を返す。
get_session_data(SessionId) ->
    case SessionId of
        "aaabbbcccddd" ->
            [{"id", "1"},
             {"name", "shin"}, 
             {"age", 36}, 
             {"sex", "male"},
             {"lang", ["ruby", "objective-c", "erlang"]},
             {"loggedin", true}];
        "eeefffggghhh" ->
            [{"id", "2"},
             {"name", "tuna"}, 
             {"age", 3}, 
             {"sex", "male"},
             {"lang", ["cat-lang"]},
             {"loggedin", true}];
        _ ->
            []
    end.

subst(Template, Values) ->
    list_to_binary(lists:flatten(io_lib:fwrite(Template, Values))).

いろいろとつっこみ所があるのは間違いないです(笑)
まあ、何はともあれ遊んでみましょうw

動作検証
今回はchromeを使いました。chromeではDeveloper Toolsにクッキーの値が見れるツールがあるので便利です。

まずはいつも通りErlangノードを起動してコンパイルします

$ erl -pa ../mochiweb
c(session_action).

9999番ポートでWebサーバを起動します。

session_action:start(9999)

ブラウザで http://localhost:9999 にアクセスすると。

ちなみにこの時点でのクッキーは

なんもありません。

では、この状態で認証が必要な http://localhost:9999/secure_space へアクセスしてみます。

見事に蹴られましたw

では
http://localhost:9999/auth/login?username=shin&password=mypassword1
にアクセスしてログインしてみます。ちなみに、ここでは思いっきりgetパラメータとしてユーザIDとパスワードを渡してますが、これは本番ではやっちゃいけないです。必ずpostとして渡すべきです。
ここではpostログインの為のページ作るのめんどくさいのでgetで渡していますw
結果は

おkみたいです。あっさりしてて実感ができませんがログインできました。

再び、さきほど表示出来なかったページを開いてみます。
http://localhost:9999/secure_space

おお〜。表示されました。

ちなみにこの時のクッキーの状態は

お、ちゃんとセッションIDとユーザIDが保存されています。

所感
今回はまずはコード内で全てすましてセッションとそれを使ったログイン機構を実装してみました。
殆どの場合、こういったものはフレームワークプラグインとして実装されているのであまり自分で実装する機会は少ないですが実際やってみると割と簡単ですね。
次回は何らかのキーバリューストアを使って実際にセッションデータを保存してみたいと思います。ErlangについてくるMnesiaを使うか、少し前にこのブログでも取り上げたredisを使うか悩んでるのですがどうしよう...

ちなみに僕は実際の開発でも、こんな感じでとりあえずハードコーディングしておいて後から関数/メソッドの中を本来の形に実装する事があります。いっぺんに全てをちゃんと実装するより僕の場合はこっちのがやりやすいです。