iPhoneアプリの使用状況をGoogle Analytics for iOSで解析する

Google AnalyticsにはiOS版があり、アプリ内の画面操作に仮想的なURLを割り当てることで、Webサイトと同じように”アクセス解析”ができる。

インストール

公式サイトからSDKをダウンロードして展開、Libraryディレクトリの中に入っている GANTracker.h と libGoogleAnalytics.a を自分のプロジェクトにコピーする(もう一つの libGoogleAnalytics_NoThumb.a はThumb命令を無効にしたプロジェクトで使用するもの。標準のプロジェクトでは必要ない)。

次にXcodeでプロジェクトファイルを開き「TARGETS」でアプリを選択、「Build Phases」「Link Binary With Libraries」を開いて、「+」ボタンを押してCFNetworkフレームワークとlibsqlite3.0.dylibを追加する。ちなみに libGoogleAnalytics.a はプロジェクトにコピーした段階で追加されているはず。

使用方法

公式サイトのドキュメントを読めばわかる通り、トラッキングの概念や使い方はWeb用のGoogle Analyticsとほぼ同じなので、迷うことはないだろう。ただトラッキング用のメソッドを実行してもすぐにはGoogleサーバーにデータが送られず、一定の時間(初期化時に dispatchPeriod として指定する)ごとにまとめて送られるという動作が異なる。

あと GANTracker.h のコメントにはサイトに書いていない有用な情報が含まれているので、そちらもチェックした方が良い。下記の便利マクロに使っている知識もそこから得た。

便利マクロ

http://d.hatena.ne.jp/ninjinkun/20110115/1295074900

ここで紹介されているマクロが便利なので改造して使わせていただいている。自分で追加・変更した内容は以下の通り。

初期化マクロ

 GA_INIT_TRACKER(ACCOUNT, PERIOD, DELEGATE)

GAアカウントと送信間隔とdelegateの設定を行う、初期化用のマクロ。定義は下のようになる。

#define __GA_INIT_TRACKER(ACCOUNT, PERIOD, DELEGATE) \
[[GANTracker sharedTracker] startTrackerWithAccountID:ACCOUNT \
                            dispatchPeriod:PERIOD \
                            delegate:DELEGATE];
 
#ifdef DEBUG
#define GA_INIT_TRACKER(ACCOUNT, PERIOD, DELEGATE) { \
  __GA_INIT_TRACKER(ACCOUNT, PERIOD, DELEGATE); \
  [GANTracker sharedTracker].debug = YES; \
  [GANTracker sharedTracker].dryRun = YES; }
#else
#define GA_INIT_TRACKER(ACCOUNT, PERIOD, DELEGATE) __GA_INIT_TRACKER(ACCOUNT, PERIOD, DELEGATE);
#endif

DEBUGマクロが定義されていた場合限り debug プロパティと dryRun プロパティが有効になる。debug プロパティを有効にするとデータがサーバに送られるタイミングでコンソールにログが出る(生の__utm.gifに対するリクエストログ)。dryRun プロパティを有効にすると実際のサーバにはデータが送られない。

ページトラッキング

GA_TRACK_PAGE(PAGE)

元サイトのものとほぼ同じだが、trackPageview:withError: は先頭に / がない場合は自動で付加してくれるので、その部分の処理を省いている。この方が / 付きのパス(NSURLから得たパスなど)をそのまま使えるので便利。

#define GA_TRACK_PAGE(PAGE) { \
  NSError *error; \
  if (![[GANTracker sharedTracker] trackPageview:PAGE withError:&error]) { NSLog(@"%@",error.helpAnchor);  } };

UITableViewのスクロール位置をView再読み込み時に復帰する方法

メモリ警告によってViewが破棄された場合、UITableViewのスクロール位置はリセットされてしまう。再度Viewが読み込まれたときに元の位置に戻すには、破棄される直前のスクロール位置を覚えておく必要がある。だいたい次のようなコードになる:

ScrollTestViewController.h

#import <UIKit/UIKit.h>
 
/* UITableViewControllerではなくUIViewControllerを使っている理由は後述 */
@interface ScrollTestViewController :
UIViewController<UITableViewDataSource, UITableViewDelegate> {
 
    /* UITableViewはIBOutletでretainする想定 */
    UITableView *_tableView;
 
    /* Viewが破棄される直前のスクロール位置 */
    CGPoint lastScrollOffset;
}
@property (nonatomic, retain) IBOutlet UITableView *tableView;
 
@end

ScrollTestViewController.m

@implementation ScrollTestViewController
 
@synthesize tableView = _tableView;
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    /*
     reloadDataでデータを読み込んだ後に
     記録されているスクロール位置を復帰
     初期化直後は{0,0}で初期化されているので影響はない
    */
    [_tableView reloadData];
    [_tableView setContentOffset:lastScrollOffset];
}
 
- (void)viewDidUnload {
    [super viewDidUnload];
 
    /*
     スクロール位置を記録した後でUITableViewを破棄する
     viewDidUnloadが呼ばれた時点でメインのViewは既に破棄されているが、
     Controllerがretainしているサブビューはまだ生きているというのがポイント
    */
    lastScrollOffset = [_tableView contentOffset];
    self.tableView = nil;
}
 
- (void) dealloc {
    self.tableView = nil;
    [super dealloc];
}
 
/*
...
その他、UIViewControllerでUITableViewを使うためには
viewWillAppearで選択を解除する処理などが必要だが
本題には関係ないので省略
...
*/
 
@end

ここでUITableViewControllerを使ってしまうと、UITableViewが破棄されるタイミングや再読み込みされるタイミングを制御できなくなるので、記憶するのも元に戻すのも難しくなる。例えばUITableViewのreloadDataをオーバーライドする必要があったりするらしい。

参考: iPhone 画面切り替え時、あらかじめテーブルビューを目的の位置にスクロールさせとく技

muninの集計負荷をtmpfsで低減する(集計時間の短縮も)

リソース監視ツールのmuninは手軽に使えて素晴らしいのだが、監視対象のサーバが増えてくると集計サーバの負荷(主にディスクI/O)が馬鹿にならなくなってくる。そこでこちらのサイトを参考にして、データファイルとHTMLファイルをtmpfs上に移動してみた。

環境

  • CentOS 6
  • munin-1.4.5 (EPELリポジトリからインストール)
  • メモリ: 8GB

データファイルとHTMLファイルの場所は /etc/munin/munin.conf の dbdir と htmldir で変更できる…はずなのだが、プラグインの中には /var/lib/munin のパスを決め打ちにしているものがあるらしく、dbdir を変更するとうまく動かなかった。仕方がないのでHTMLファイルを /var/lib/munin 以下に移動して、tmpfs を /var/lib/munin にマウントすることにした。

まずHTMLファイルを移動する。以下の操作はroot権限で、muninの集計を停止してから行う。

muninの集計を止めるには、munin-cronの実行権限を外すか、/etc/cron.d/munin を編集してcronによる定期実行を止める。

# munin-cronの停止
chmod ugo-x /usr/bin/munin-cron
 
# 念のためmuninプロセスが動いていないか確認しておく
ps aux | grep munin

次にHTMLファイルをディレクトリごとコピー、そのパスにあわせてmuninとApacheの設定を変更する。

cp -a /var/www/html/munin/ /var/lib/munin/html
 
vim /etc/munin/munin.conf
# htmldir /var/lib/munin/html
 
vim /etc/httpd/conf.d/munin.conf
# 私の環境ではcgiディレクトリは /var/www/munin/cgi に移してあるので
# ScriptAlias /munin/cgi/ /var/www/munin/cgi/
# Alias /munin/ /var/lib/munin/html/

ここまで終えたら一旦muninの集計を再開し、正しく動くことを確認しておく。確認できたら再び集計を停止して、tmpfsの設定に移る。tmpfsに割り当てる容量だが、とりあえず参考サイトに従って1GBとした。二十数サーバを数年間監視した結果のデータ+HTMLファイルが300MB弱だったので、1GBあれば当分は大丈夫だろう。

vim /etc/fstab
#tmpfs  /var/lib/munin  tmpfs  rw,size=1024M   0 0

一旦データを退避してから /var/lib/munin にtmpfsをマウントし、退避しておいたデータを戻す。

mv /var/lib/munin /var/lib/munin.backup
mkdir /var/lib/munin
mount /var/lib/munin
cp -a /var/lib/munin.backup/* /var/lib/munin/

これでmuninが更新するファイルは全てメモリ上に置かれることになった。が、このままだとマシンが再起動されるとデータが消滅してしまうので、バックアップ策を講じる必要がある。参考サイトではcronを使って1時間ごとにバックアップを行っているが、それだと更新中の不完全なデータがバックアップされる危険性がある。そもそもmuninの集計はcronで5分ごとに実行されるのだから、次のような方針でバックアップ/リストアをすることにした。

  • 30分に一回、集計の「後に」データをバックアップする
  • 毎回、集計の「前に」データの有無をチェックして、なければデータをリストアする。このチェックは単純にファイルの有無を調べるだけなので、5分ごとに実行しても負担にはならない

このためにmunin-cronをラップする次のようなシェルスクリプトを書いた。

#!/bin/sh
 
rsync=/usr/bin/rsync
munin_cron=/usr/bin/munin-cron
munin_dir=/var/lib/munin
backup_dir=/home/munin/backup
lock_file=/var/tmp/munin-cron-backup.lock
 
die() {
        echo $1
        exit 1
}
 
lock() {
        test -e $lock_file && die "Backup/retore process is already runnning" 
        touch $lock_file
}
 
unlock() {
        rm -f $lock_file
}
 
backup() {
        lock || die "Failed to lock the backup process"
        $rsync -au $munin_dir/ $backup_dir/
        unlock
}
 
restore() {
        lock || die "Failed to lock the restore process"
        if [ ! "(" -e $munin_dir/datafile -a -d $munin_dir/html ")" ]; then
                $rsync -au $backup_dir/ $munin_dir/
        fi
        unlock
}
 
restore
 
test -x $munin_cron && $munin_cron
 
if [ "$1" = "backup" ]; then
        backup
fi

このスクリプトでは datafile というファイルと html ディレクトリの存在をもって、「データが存在する」と判断する。一応、同時実行を避けるための簡易的なロックも付けてみた。5分に一回しか実行されないと分かっているのだから、この程度でも多少は意味があるだろう。各種コマンドやバックアップ先などのパスは好みで変えて欲しい。私はこのシェルスクリプトやバックアップを /home/munin 以下にまとめることにした。

あとはmunin-cronの代わりに、/etc/cron.d/munin の中でこのスクリプトを実行するよう設定する。30分に一回は引数に backup を与えて実行することで、バックアップが作成される。

MAILTO=root

0,30 * * * * munin /home/munin/bin/munin-cron.sh backup
5,10,15,20,25,35,40,45,50,55 * * * * munin /home/munin/bin/munin-cron.sh

リストアは集計前に勝手に行われるので、再起動しても何もしなくていい。

最後に実施前後のCPU使用率のグラフを載せておく。確かにI/O待ちはガクンと減った(ちょっとだけ出ているのはバックアップ)。

CPU使用率の変化

それから集計に要する時間も多少は短縮されたようだ。もっともネットワーク経由でクライアントに接続する部分がボトルネックになっているようなので、あまり結果は安定していない。

munin集計時間の変化

Rails3のi18n機能および日本語化

必要な情報はRails Guidesの中の以下のページにまとまっている。

Rails Internationalization (I18n) API

  • I18n.translate メソッドを使って「抽象化されたキー」から、ロケールに基づく「自然言語」に変換する。いきなり英語で文章を考えなくて済む分、日本人には取っ付きやすいかもしれない。同様に、 I18n.localize メソッドで日付や時刻のフォーマットを行う。
    • それぞれ I18n.t, I18n.l という省略形が用意されている。さらに多くのコンテクストでは t, l というショートカットメソッドが使える。
  • Railsフレームワーク自体もこのメカニズムで国際化されているため、各コンポーネントが要求するキーに対して適切な翻訳を用意することで、フレームワーク全体が翻訳される。
  • キーは階層化することができる。またRailsが要求するキーも、コンポーネントごとに階層化されている(例: activerecord.errors.messages.record_invalid など)
    • 標準のメッセージファイルにおいては、YAML(.yml)またはRuby(.rb)のハッシュを使って、キーの階層をそのまま再現する。
    • 階層の上位部分をスコープ(scope)と呼ぶが、どこまでがスコープかという区別は明確ではない。
  • I18n.locale= で使用するロケールを設定する。
    • 使用するロケールの決定方法について。Web”サイト”においてはURL中にロケールを含めるべきである。一方で、Web”アプリケーション/サービス”においてはAcceptヘッダやDBに保存した個人設定を使うことも有効である。

フレームワーク全体の日本語化

とにかく手っ取り早く”日本語化”をしたいという場合は

https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml

を config/locales/ に放り込んで、config/application.rb に

config.i18n.default_locale = :ja

と書いておけば、フレームワークが自動で出力する各種メッセージは日本語になる。ただ標準ではエラーメッセージのフィールド名の後に空白が入るので、errors.formatを次のように修正しておいた方が良いかもしれない(好みの問題だが)。

--- ja.yml.orig     2011-06-22 22:38:21.000000000 +0900
+++ ja.yml     2011-06-22 22:38:33.000000000 +0900
@@ -188,7 +188,7 @@
       submit: "保存する"
 
   errors:
-    format: "%{attribute} %{message}"
+    format: "%{attribute}%{message}"
 
     messages: &errors_messages
       inclusion: "は一覧にありません。"

以後、この ja.yml に自分のアプリ用のメッセージを追加していっても良いが、やはり専用の翻訳ファイルを追加した方が扱いやすいだろう。翻訳ファイルは config/locales/ の中に拡張子 .yml か .rb で作成する。ファイル名は何でもいいし、言語ごとに分けてもいいし分けなくてもいい。ただしサブディレクトリの中に入れた場合は追加の設定が必要になる(参照 3.4 Organization of Locale Files)。また新たにファイルを追加した場合はサーバを再起動しなければならない。

ActiveRecordのモデル名・フィールド名の日本語化

上の手順でvalidationのエラーメッセージは日本語化されるが、モデル名やフィールド名は自分で翻訳を用意する必要がある。詳細については下ページ参照。

Translations for Active Record Models

ActiveModelのモデル名・フィールド名の日本語化

任意のクラスにおいて ActiveModel::Translation モジュールをextendすることで、ActiveRecord同様の翻訳機能が有効になる。

class MyForm
  extend ActiveModel::Translation
end

Active Model Translation

ただしスコープが activerecord ではなく activemodel であることに注意(サブクラスで i18n_scope メソッドをオーバライドすれば変更できるが)。

activemodel:
  models:
    my_form: "モデル名"
  attributes:
    my_form
      attr_name: "属性名"

なおvalidationに関してはActiveRecordと共通である。

Rails3+Rspecでファイルアップロードのテストをする

標準のTest::Unitに含まれている fixture_file_upload はrspec-railsに含まれていないので、自分でspec_helper.rbに追加する。

def fixture_file_upload(path, type)
  Rack::Test::UploadedFile.new(fixture_path + path, type)
end

標準の fixture_file_upload 同様、Rack::Test::UploadedFile のインスタンスを返すだけ。

テストに使用するファイルは spec/fixtures/ 以下に置く。例えば spec/fixtures/files/dummy.txt を使用するには

fixture_file_upload("/files/dummy.txt", "text/plain")

Rails3でjQueryを使う場合の注意点

新規プロジェクトの作成時に -J オプションを付けてprototype.jsの読み込みをスキップした場合、config/application.rb の中で以下の設定が有効になる。

config.action_view.javascript_expansions[:defaults] = %w()

あとからjQueryを使うためにjquery-railsをインストールしたとき、この設定が有効なままだとビューの中で

javascript_include_tag :defaults

を書いてもjquery.js等が読み込まれない。

忘れずにコメントアウトしておく(コメントアウトすることで、jquery-railsが自動設定した値が有効になる)。

#config.action_view.javascript_expansions[:defaults] = %w()

あるいは、jqueryとapplication.js以外に常時読み込みたい.jsファイルがあるのなら、+= で配列に追加しておく。

config.action_view.javascript_expansions[:defaults]+= %w(mylib.js otherlib.js)

CouchDB 1.1.0をMac OS Xにインストール

公式WikiではHomebrewによるインストールが推奨されているので、brewコマンドで普通にインストールする。

brew install couchdb

Erlang R14B03 と couchdb1.1.0 がインストールされた。

この後Wikiの手順では

  • 手動でcouchdbユーザとcouchdbグループを作る
  • /usr/local 以下の所有者とパーミッションを変更して、/Library/LaunchDaemons/ に起動スクリプトをコピー
  • sudoを使ってcouchdbを起動/停止するaliasを設定

となっているが、brewで管理している /usr/local 以下の所有者を変更するのはちょっと嫌だし、どうせ開発用に使うだけだし、そもそもインストール後に表示されるメッセージには

If this is your first install, automatically load on login with:
    mkdir -p ~/Library/LaunchAgents
    cp /usr/local/Cellar/couchdb/1.1.0/Library/LaunchDaemons/org.apache.couchdb.plist ~/Library/LaunchAgents/
    launchctl load -w ~/Library/LaunchAgents/org.apache.couchdb.plist

と書いてあったので、起動スクリプトは ~/Library/LaunchAgents/ にコピーして、次のようなエイリアスを設定することにした。

alias restart_couch='launchctl stop org.apache.couchdb'
alias start_couch='launchctl load -w ~/Library/LaunchAgents/org.apache.couchdb.plist'
alias stop_couch='launchctl unload ~/Library/LaunchAgents/org.apache.couchdb.plist'

起動したり停止したりするのに sudo は不要。

start_couch
#起動
 
curl http://localhost:5984/
# =>{"couchdb":"Welcome","version":"1.1.0"}
 
stop_couch
#停止

設定ファイル等は /usr/local/etc/couchdb/ にある。

IBOutletで接続した変数はreleaseする必要があるのか

iOSとMac OS Xとで結論が異なる。OS X開発経験者が書いた書籍やWebサイトはこの違いを考慮していない場合があるので注意が必要。

……

IBOutletの宣言はインスタンス変数にもプロパティにも書くことができる。

@interface IBOutletTestAppDelegate : NSObject <UIApplicationDelegate> {
    UILabel *_retainLabel;
    UILabel *_assignLabel;
    IBOutlet UILabel *_variableLabel;
}
 
@property (nonatomic, retain) IBOutlet UILabel *retainLabel;
@property (nonatomic, assign) IBOutlet UILabel *assignLabel;
@end

iOSの場合、IBOutletをどちらに書いても、アウトレットの接続は常にNSObject(NSKeyValueCoding)の setValue:forKey: メソッドを使って行われる。このメソッドは(1)まず対応するプロパティのsetterを探して、(2)見つからない場合はインスタンス変数に代入してretainカウントを1増やす。よってassignプロパティを使うのでない限り、アウトレットで接続した変数は常にreleaseしなければならない

この動作は setValue:forKey: をオーバーライドしてみれば確認できる。

- (void)setValue:(id)value forKey:(NSString *)key
{
    //retainのプロパティとインスタンス変数の場合は
    //retainCountが1増える
    NSLog(@"[before setValue] %@ %d", key, [value retainCount]);
    [super setValue:value forKey:key];
    NSLog(@"[aflter setValue] %@ %d", key, [value retainCount]);
}

ただしAppleが提供するGuideでは常にプロパティを使うことを推奨している(Memory Management Programming Guideの『Memory Management of Nib Objects』など)。プロパティを使うことでretain/assignのポリシーが明確にできるし、retain/assignどちらの場合もviewを破棄する際にはnilを代入するだけで良い。特にretainの場合はreleaseとnil代入の操作を一度にできるので便利である。

……

Nibファイルによるオブジェクトの生成と、生成されたオブジェクトのretain状況についてはResource Programming Guideの『The Nib Object Life Cycle』にまとめられている。これを読むとOS Xの方が(GCを使わない場合)状況が複雑だということがわかる。

簡単にまとめると

iOSの場合

  • 生成されたオブジェクトは全てautorelease済みである
  • アウトレットの接続は setValue:forKey: メソッドで行われる

OS X(GCなし)の場合

  • 生成されたオブジェクトは、トップレベルのオブジェクトを除いてautorelease済みである。
  • アウトレットの接続は、(1)まずset{OutletName}:というメソッドを探して、(2)見つからない場合はインスタンス変数にretainカウントを増やさずに代入される

OS X(GCなし)の場合、トップレベルのオブジェクとそれ以外のオブジェクトとで扱いが異なるので、一貫したポリシーを立てるのが難しくなっている。もしretainのプロパティを使った場合、トップレベルのオブジェクトは2回releaseしなければならない。もしインスタンス変数に直接代入した場合、トップレベルのオブジェクトに限りreleaseしなければならない。

External Objectを使ってIBOutletからNibファイル外のオブジェクトに接続する

External Objectを使うことで、Nibファイル外のオブジェクトに対してIBOutletで接続できる。ちなみにこの機能はMac OS Xでは使用できず、iOSでのみ使える。

……

たとえばあるカスタムビューからモデルのインスタンスを参照したいとする。このビューでは常にモデルの状態を反映した表示を行いたいので、インスタンス変数にモデルへの参照を保持しておきたい。

ビューの生成・初期化をプログラム中で行うなら、initWithModel:のような初期化子を用意しておけば済む。しかしビューをNibファイルから生成する場合は初期化時にモデルを参照することができない(モデルは普通Nibファイルで生成しないので)。仕方がないのでモデルのインスタンスだけはUIViewControllerのviewDidLoadでビューに代入して、専用の初期化メソッドを呼び出すなどするしかない。これは非常に面倒だし忘れやすい。

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    //プロパティに代入してから表示を更新。面倒くさい
    _customView.model = _model;
    [_customView update];
 }

そこでExternal Objectの出番である。このオブジェクトはNibファイル外の既成のオブジェクトに対するPlaceholderとして機能する。ちょうど自由な名前をつけられる「File’s Owner」だと考えれば良い。

使い方は次のようになる。まずカスタムビューの.hファイルで、モデルに対するOutletを設定しておく。

@interface CustomView : UITableViewCell
{
    Model *_model;
}
 
@property (nonatomic, retain) IBOutlet Model *model;

モデルを使った初期化処理は、その他の初期化処理と同じくawakeFromNibの中に書くことができる。

- (void)awakeFromNib
{
    [super awakeFromNib];
 
    //モデルのデータを使って表示を初期化する
    _textField.text = _model.name;
}

続いてxibファイルを開いてExternal Objectの設定を行う。Libraryの中から「External Object」を選んでObjectsの欄にドラッグアンドドロップすると、Placeholdersの欄にオブジェクトが追加される。

追加されたオブジェクトを選択してIdentity inspectorを開き、「Custom Class」の中でモデルクラスのクラス名を指定する。

次にAttributes inspectorを開き、Identityの欄にxibファイル中で一意な識別子を設定する。この識別子が実際のオブジェクトを割り当てる際のキーになる。

あとは通常のOutlet接続と同じ操作で、カスタムビューのOutletプロパティからPlaceholders内のモデルに対して接続を行っておく。

これでxibファイルの準備ができたので、あとはNib読み込みの際にExternal Objectとして参照されるオブジェクトの実体を指定する。これにはNSBundleクラスの loadNibNamed:owner:options: メソッドの UINibExternalObjects オプションを使う。

Nibファイルの読み込みはUIViewControllerのloadViewメソッで行うのが良いだろう。以下のサンプルコードではコントローラがモデルのインスタンスを変数 _model に保持しており、xibファイル内でExternal Objectには Model というidentityを割り当てたと想定している。YourNibFileNameは読み込むNibの名称。

- (void)loadView
{
    NSArray *topLevelObjs = nil;
    NSDictionary *proxies = [NSDictionary dictionaryWithObject:_model forKey:@"Model"];
    NSDictionary *options = [NSDictionary dictionaryWithObject:proxies forKey:UINibExternalObjects];
    topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"YourNibFileName" owner:self options:options];
 
    if ([topLevelObjs count] == 0) {
        NSLog(@"Warning! Could not load nib file.\n");
        return;
    }
}

これによって特別な初期化操作をしなくても、Nibファイル内のカスタムビューとコントローラとで一つのモデルのインスタンスを共有することができる。

参考: Resource Programming Guide

Eee PC 1015PEMにUbuntu 10.10インストール(無線LANの問題解消)

追記(11.10用) 下記の設定を行った状態で11.10にアップデートしたら無線LANが認識されなくなった。/etc/modprobe.d/blacklist.conf で次の1行をコメントアウトして再起動したら復活した。

#blacklist rt2800pci

ASUSのネットブックEee PC 1015PEMを買った。標準で入っていたWindows 7 Starterを使う気はさらさらなかったので、リカバリ領域をUSBメモリにバックアップした後、HDD丸ごと使ってUbuntuをインストールした。もうすぐ11.04が出るのだが、とりあえず10.10を選択。

1015PEMには光学ドライブはないのでインストールはUSBメモリから。手順はUbuntu公式の「Netbook」のダウンロードページに書いてある。まずWindowsでUniversal USB Installerをダウンロードして、次にUbuntu Japanese Teamのサイトから10.10 Desktop 日本語Remixのisoイメージを落とし、Universal USB Installerを起動してUSBメモリに書き込んだ。

あとはUSBメモリを刺して起動時にBIOSでboot deviceを指定、画面に従ってインストールするだけ。ちなみにこのときUSBメモリから直接起動して動作をテストすることもできるが、その動作は実際にインストールしたときの動作とは必ずしも一致しないことに注意。私の場合、この方法で起動すると無線LANが全く繋がらなかったり、日本語入力が使えなかったりした。

インストール自体は30分ほどで終了。早速無線LANに繋いでソフトウェアアップデートをかけるが、どうもネットワークの速度が遅い。pingを打ってみると30%〜ほどのパケットが欠落している。有線LANに繋ぎ変えると正常なので、明らかに無線LANがおかしい。Windows 7で使ったときには正常だったから、問題は無線LANアダプタのドライバだろうと当たりをつけて調査開始。

結論から言うと、次の操作を行うことで解消した:

/etc/modprobe.d/blacklist.conf に以下の内容を追記して、マシンを再起動。

blacklist rt2800pci
blacklist rt2800lib
blacklist rt2800usb
blacklist rt2x00usb
blacklist rt2x00pci
blacklist rt2x00lib

問題を解消するにあたってはこのページが非常に役に立った。

Ubuntu 10.04 LTS でEeePC 1015PEMで無線LANを使う(Ralink RT3090) – Ubuntuスタイル

まずlspciコマンドを実行してみると、確かにRaLink社のRT3090というアダプタが搭載されているのが確認できた。そこでRaLink社のサイトを見に行ったところ、Linux用のドライバが配布されていたので、早速「RT3090」用のファイルをダウンロードした。zipファイルを展開して中を見てみると、このドライバは「RT2860STA」という名称だと判明した。

ここでインストールする前に念のため lsmod | grep rt で現在読み込まれているドライバを確認してみたところ、既にrt2860staは読み込まれている、つまりUbuntu 10.10にデフォルトで含まれていたということが分かった。もしかしたらバージョンが古いのかとも思ったが、公式で配布されているドライバの更新日時からするとそれも考えにくい。

しかしさらに注意してlsmodの結果を見てみると、rt2800pciやrt2x00pciといったRaLink社の他のドライバも読み込まれていることに気づいた。これが悪さをしているのかもしれないと思い、このページに従って前述のとおりに /etc/modprove.d/blacklist.conf を修正し再起動したところ、問題は解消した。

なお以上はすべて10.10での話だが、この「一応は無線LANに繋がるが頻繁にパケットロスする」症状は11.04のUSBメモリから起動した時にも遭遇した。もしかすると11.04でも有効な手順かもしれない。