Firefox OS 端末でメモリ不足となった時、低メモリキラーや低メモリ通知が、プロセスを停止してOSの実行を続けるために動作します。カーネルがフォアグランドプロセスを停止する時、使用中のアプリは明らかにクラッシュします。この記事ではOOM(out of memory; 低メモリ) クラッシュを理解、デバックする方法を説明します。
記: Firefox OSで低メモリ状況がどう管理されているかについてまだ知らない場合、この文書を続ける前に Firefox OSの低メモリ管理 を読むことを推めます。
OOMクラッシュをデバッグする
電話機がメモリ不足により起こると疑われる、再現できるクラッシュをするとします。下記の対策を取ると、何が悪いかをもっと理解できます。
ステップ1: それが実際にOOMかどうか検証する
まず、実際クラッシュが電話機がメモリ不足で動作しているためかどうか確認する必要があります。そうするには、 adb shell dmesg
を実行します。アプリがOOMのために停止している場合、下記のような反応を見ることができるでしょう:
<4>[06-18 07:40:25.291] [2897: Notes+]send sigkill to 2897 (Notes+), adj 2, size 30625
この行は、電話機の低メモリキラーがNotes+ アプリ(process id 2897)を、停止時に oom_adj 2
が起きたために強制停止したことを示しています。ここで報告されるサイズは各 4kb のページサイズです。よってこの場合、Notes+ アプリは 30625 * 4kb = 120mb のメモリを使用しています。
脱線: OOMでない
dmesg
出力にこうした行が見えない場合、クラッシュは OOM でないことが見込まれます。そんなクラッシュをデバッグする次の手は、gdb
にクラッシュプロセスをアタッチして、下記のようにバックトレースを得ることです:
$ cd path/to/B2G/checkout $ adb shell b2g-ps # Note pid of the app that you're going to crash $ ./run-gdb.sh attach <pid> (gdb) continue # crash the app (gdb) bt
バグ報告時に、adb logcat
の出力と一緒に、この出力を添付します。クラッシュが OOM のせいであればgdb
backtrace は多分興味深くないもので、なぜなら OOM クラッシュは、プロセスが実行する悪いコードをのせいでなく、カーネルから送られるシグナルによって引き起こされるからです。
ステップ2: メモリレポートを集める
クラッシュが実際に OOM によるものだと検証した後、次のステップはアプリがクラッシュする前に電話機のメモリレポートを集める事です。メモリレポートはどこでメモリが使われているかを理解するのに役立ちます。このステップは、一旦アプリがクラッシュすると、そのプロセスからメモリレポートを集める方法がないため、少し扱いにくいです。またカーネルがプロセスを強制停止しようとする時にはメモリレポートを起動させる方法もないです — その時には、遅すぎます。
電話機からメモリレポートを取得するには、まずビルドツリーを更新して、関連ツールの最新版を入手します。repo sync
は十分ではありません; git fetch && git merge
または git pull
が必要です:
$ cd path/to/B2G/checkout $ git fetch origin $ git merge --ff-only origin
今やメモリレポートツールをこのように実行できます:
$ tools/get_about_memory.py
幸運にもメモリレポートを手に入れた時、ディレクトリを(about-memory-N
と名づけた)zipにまとめて関連するバグに添付します。しかしまたもや、これはアプリがまだ生きていて大量のメモリを使っている時にコマンドを実行した場合のみに有用です。いくつかのオプションがあります。
ステップ2, オプション1: 他の端末を入手する
しばしば最も簡単な事は、もっと多くのRAMを持つ端末を入手する事です。上記ステップ 1ではプロセスがクラッシュする時にいかに大量のメモリを使うかが分かったでしょう、なので単にプロセスがそれだけの量のメモリを使うのを待って、メモリレポートを取ります。 b2g-info
ツールは様々なB2Gプロセスがいかにメモリを使っているかを示します。 下記のようにする事で、ループ内でこのツールを実行できます:
$ adb shell 'while true; do b2g-info; sleep 1; done'
あなたの端末で b2g-info
が利用できない場合、b2g-procrank
を代用できます。
ステップ2, オプション2: Fastest finger
よりたくさんのRAMのある端末にアクセス出来ない場合、アプリがクラッシュする直前に get_about_memory.py
を試行できます。またもや、 b2g-info
をループ内で実行できて(前節に示した通り)、 いつ run get_about_memory.py
を実行するかを理解できます。メモリレポートを実行するとしばらく電話機の全プロセスが停止し、よってあるプロセス自身がOOMを起こす前にメモリレポートを捉えるのはしばしば難しくありません。
ステップ2, オプション3: より小さなテストケースを使う
"アプリ内でサイズが X 以上のファイル"を扱う時に、しばしばOOMに遭遇します。
サイズXのテストケースでアプリのクラッシュが速すぎる場合、同様だが小さめ(まぁ、X/2 サイズ)のテストケースを実行して、それが成功してからメモリレポートを取得する事を試せます。この方法で生成されたメモリレポートは、しばしば最終的に考慮するOOMクラッシュへの良い洞察を与えてくれます。
ステップ2, オプション4: デスクトップ版B2Gを実行する
最悪中の最悪の場合、FxOS電話機よりずっと多いRAMを持つ、デスクトップ版B2Gを実行できます。これはデスクトップ機でB2Gを実行するのは電話機でB2Gを実行するのといくつか異なる点があるため、扱いにくいです。
特に、デスクトップ機の B2G はデフォルトでマルチプロセス無効になっています。実際いろいろな場所で 100% 正しく動きません、 しかし Linux と Mac では (Bug 923961, Bug 914584, Bug 891882に従って) 最も正確に動作します。 マルチプロセスなしでデスクトッブでテストできますが、私の経験ではプロセス間通信のコードにより高いメモリ使用の問題が起こり、よって見ているバグを引き起こします。
B2G デスクトッブプロセスからのメモリレポートを取得も便利という程ではありません。Linuxではシグナル34 をメイン B2G プロセスに送れることができて、それは memory-report-*.gz
ファイルを /tmp
に出力します。
B2G デスクトップビルドを使うメリットは、好きなデバッグツール、例えば OSX についているやつを使えることです。We've had a lot of success with this in the past. To collect a memory report using Instruments on OS X, choose "New -> Mac OS X -> Allocations". Start b2g-desktop and you should see multiple "plugin-container" processes in the activity monitor. You will need 2 Instruments activities: 1 to trace the allocations on the main b2g process and another to trace the allocations on the app you wish to analyze. Attach the instrument activities and execute your test case.
To analyze how much memory your app is using, analyze call trees. Check the "Invert Call Tree" tick, and sort by bytes used. This will show you which part of your app is using lots of memory. Below is a screenshot of a sample analysis of memory usage for an app:
For more information on setting up B2G desktop builds, read our Hacking Gaia page.
ステップ3: メモリレポートを解析する
When you run get_about_memory.py
, it will open a memory report in Firefox. This file contains information about the memory usage of all processes on the system. Reading these reports can be a bit overwhelming at first, but it's not so bad once you get the hang of it. Note that you can hover over any leaf node to get a description of what that node describes. What you're looking for is something "unusually large" in the crashing process. You can get an idea of what "unusually large" means by capturing a memory report of your app when it's not using a ton of memory and comparing that to the errant memory report.
Reading memory reports takes some practice, so feel free to ask for help. The experts on this subject hang out in #memshrink on IRC.
ステップ4: 必要に応じ、DMD付きでリビルドする
One common line item to stick out in memory reports captured before apps crash is heap-unclassified
. heap-unclassified
counts memory allocated by the process that isn't covered by any other memory reporter. If you have high heap-unclassified
, the memory report can't tell you anything else about what that memory belongs to. Our tool for digging into heap-unclassified
is called DMD. This works on B2G, but you must build B2G yourself in order for it to work because DMD requires local symbols that are only kept on the build machine.
To find out more information on running DMD and interpreting its output, read the DMD documentation.