Unyablog.

のにれんのブログ

Mackerel Agent for Android

この記事は Mackerel アドベントカレンダー 21 日目の記事です。

qiita.com

Mackerel について

Mackerel とはサーバーを監視するサービスで、Slack などとの連携が便利だったりグラフが美しかったりと良いサービスです。

Mackerel にデータを送るためのサーバー監視ソフトウェアとしては mackerel-agent というものがあり、これをインストールするとサーバー上でメトリックを定期的に取得し、送信してくれます。

github.com

本題

今回はその Android 版を作ってみました。

Mackerel Agent for Android です。

f:id:nonylene:20161221015614p:plain

昨日から PlayStore に公開しています!

f:id:nonylene:20161222000441p:plain:w150

play.google.com

GitHub はこちらです。

github.com

対応バージョンは Android 4.4 以上です。

機能

1分毎にメトリックを集め、5分毎にサーバーに送信します。Mackrel には 1 ホストとして登録するようにしています。

残念ながら mackerel-plugin は使えませんが、公式の mackerel-agent が現在送信しているメトリックと spec は全てカバーしています。

動機

実装すると proc が学べるし良いかなと思ったからです。あとバッテリー切れそうな時に通知してくれると便利かもと思いました(がまだ Android 関係のカスタムメトリックは実装していない…)。

実装等

AndroidLinux なので概ね mackrel-agent の Linux 版と同じ処理でメトリックを取得できます。

実装は Kotlin で行いました。アプリの特性上 proc ファイルをたくさん読むのですが、Kotlin だとファイル関係がかなり楽なので良かったです。

// Interface の Spec を読む例
File("/proc/net/dev").readLines()
        .map { it.split(":").map(String::trim) }
        .filter { it.size == 2 }
        // remove loopback
        .filter { it[0] != "lo" }
        .mapNotNull {
            val values = it[1].split(Regex("\\s+")).map(String::toDouble)
                // all zero -> remove data
            if (values.all { it == 0.0 }) return@mapNotNull null
            InterfaceStat(it[0].trim(),
                    values[0], values[1], values[2], values[3], values[4], values[5], values[6],
                    values[7], values[8], values[9], values[10], values[11], values[12], values[13],
                    values[14], values[15], time
            )
        }

ログや取得した値のキャッシュは Realm を使っています。proc を用いて差分を出すには数秒間待つなどの必要がありますが、そのあたりは RxJava を用いていい感じにしました。*1

Android の辛いところ

コマンド

mackerel-agent ではいくつかコマンドを打って結果をパースしている物があります。

dfuname がそれに当たるのですが、これを Android で打とうとするとバージョン問題に直面します。

Android 5.0 未満では df は一切オプション受け付けない上に独自の形式で出力され、uname においては存在すらいしていません。

結局 df では以下のようにバージョンで分けるようにして、uname は proc から取得できる値を取得することにしました。*2

MackerelAgentAndroid/FileSystem.kt at 48a67bdd9387ec519800612e847ea28bc4bacf84 · nonylene/MackerelAgentAndroid · GitHub

Android 6.0 以降では toybox のお陰でコマンド環境は向上し、大抵の必要なコマンドは使えるようになりました。

詳しくは

nonylene.hatenablog.jp

に書いています。

定期実行

Android で定期的にタスクを実行するにはいくつかの方法があります。このアプリはフォアグラウンドサービスとして常駐するようにしていますが、代表的なものは AlarmManager でしょう。

しかし、今回は Forground Service を使いました。なので通知バーに常駐しています。

これは Android 6.0 からのバッテリー節約のための新機能である Doze / AppStandby によるのものです。

developer.android.com

doze で1分間毎に実行するのが崩れる*3のは構わないのですが、問題は App Standby です。

AppStandby は、充電していない状態で一定の条件とネットワークの使用が 一日に一回 に制限されるというものです。

この "一定の条件" とは

  • The user explicitly launches the app.

  • The app has a process currently in the foreground (either as an activity or foreground service, or in use by another activity or foreground service).

  • The app generates a notification that users see on the lock screen or in the notification tray.

Optimizing for Doze and App Standby | Android Developers

となっています。

裏で毎分取得して API に送信するようなアプリはわざわざ立ち上げることも無いでしょうし、通知も出すことも無いので、バックグラウンドで走らせているとこの条件に当てはまるのは必須です。せめて端末が点いているときは送信して欲しいので、あまり推奨はされていませんが Forground Service を用いています。

Forground Service を使うと電池持ちが悪くなるように思えますが、 Forground Service で定期実行してもスリープ中は動作は遅れているので影響はほとんどありませんでした。

また、稀に CPU の使用率が -100 % になったりするみたいです。ちゃんと見ていませんが、これは CPU 使用量などの値が定期的にリセットされることによるものだと思います。結局マイナスの場合は例外を投げることにしました。

まとめ

proc を読んで Kotlin で mackerel-agent を実装していくのは楽しかったです。送信したデータを見るのも、アプリを使っている時間にメモリや CPU が使われているのが分かったりして面白いです。ぜひ入れてみてください。PR などもご気軽にどうぞ!

*1:ちなみに RxJava を Kotlin から使うと時々 SAM が辛くなったりする… https://youtrack.jetbrains.com/issue/KT-14754

*2:NDK を入れて C から System Call を呼べば取得できる気がしますが、今回は行っていません。

*3:システムがしばらく未使用状態となり Doze に入ると AlarmManager の定期実行が遅延して、許可された時間のみに行われるようになる

軽い Shell を作った

↓の記事を読んで自分でもサクッと作れそうなので作ってみた。

brennan.io

名前は nsh (nonylene shell の略)。記事を参考にコードを書いていたら大体同じようなものになってしまった。

github.com

記事では C で書いているけど、nsh では Python3 で書いてみた *1。結局パースで6割ぐらい占めていて、コマンドを実行するのはこの部分だけ。

def execute_line(args):
    pid = os.fork()
    if pid == 0:
        os.execvp(args[0], args)
    elif pid < 0:
        print("error with fork!")
    else:
        while True:
            wpid, status = os.waitpid(pid, os.WUNTRACED)
            if os.WIFEXITED(status) or os.WIFSIGNALED(status): break

それなりに使えるようになったので満足。

f:id:nonylene:20161209003000p:plain

fork して execvp している以外は通常の Python の処理なので、特段困ったこともなく一時間ぐらいで作れたので良かった(小並感)。

*1:今考えたらシステムコール打ちたい時に困りそうだけど、軽く作って見る分には os.fork や os.execvp を使う程度だったので大丈夫だった。

サーバーを ConoHa に移行した

サーバーを家の Raspberry Pi から ConoHa に移行しました。

理由は単純に Raspberry Pi ではいろいろするのに辛くなってきたのと、最近謎のエラー*1により SEGV することが多くなって悲しくなってきたためです。カーネルの Early Memtest 使って軽くメモリチェックしてみたり microSD の r / w チェックしたりしてみたけど特に異常も無くてよく分からなかった。

ConoHa にした理由は特に無くて、強いて言えばスペックが後から変更できることですが、だからと言って変更することはなさそう。

VPS の作成は速いし、コンソールもシンプルで使いやすいので良いと思います。

管理

ついでに Ansible で管理していたのを itamae に置き換えました。Ansible は Python のバージョンが面倒だったのですが itamae はそういった事もないので良いですね。

ConoHa は IPv6 アドレスを 17 個もらえるのですが、まだメインの 1 つしか使っていないのでどうしようかなと考えているところ…。

https://nonylene.net

ついでに HTTPS 化しました。Let's encrypt でシュッと作成して設定しただけです。/blog/ とか Mixed Contents 入りまくってますがもう使ってないので放置してます...(テンプレート直して jekyll 回すだけ)。

更新もletsencrypt renew を cron で回せばいいので楽ですね。

退役した Raspberry Pi は家の温度取得したりエアコン操作したり目覚ましにしたりできればいいなあと思っています。目覚ましは omxplayer 使ってスピーカー鳴らせばすぐできそうな気がする。

*1:relocate で死ぬ

Android Studio をビルドする

この記事は KMC アドベントカレンダー 4日目の記事です。自分は5日目だと思っていてのんびりしてました… :bow: :bow:

www.adventar.org

昨日は id:nojima718 さんの

nojima.hatenablog.com

でした!

本題

今回は Android Studio をビルドしてみます。

大体ここに従ってます。

tools.android.com

何故

Android Studio 2.2 になってから mac で外部ディスプレイに繋げたときに色が全体的に色が濃くなりました。僕の使ってる Molokai テーマには致命的で、元からコントラストが高いのにさらに濃くなると目が痛くて仕方ありません。

だからといってテーマ変えても濃くてつらく、最近は外部ディスプレイに繋がずに使っていました。

ここで Android Studio の更新ログを見ると

Improved color management on OS X

とのこと。

もしこれのせいなら、 該当する Commit を Revert して自分でビルドすればいいのでは? と思い立ち、とりあえず自分でビルドしてみることにしました。

ちなみに原因はそれではありませんでした(後述)。

ビルド

ソースコード

repo

Android では複数の git repository を一度に扱う repo というコマンドで管理することを推奨しています。*1

Repo command reference | Android Open Source Project

上に従って repo (実体は Python スクリプト) をダウンロードして実行できるようにします。

repo sync

次は適当な場所にソースコードをダウンロードします。とりあえずビルドしてみるだけなので --depth=1 としてファイル容量を抑えています。*2

$ mkdir studio
$ cd studio
$ repo init -u https://android.googlesource.com/platform/manifest -b studio-2.2.2 --depth=1
$ repo sync

大量のレポジトリが 3GB ほど降ってきます。後のビルドでも 5GB 程度は容量食うのでそれなりに確保しておきましょう。

ビルド

とりあえずビルドしてみます。ドキュメントにある通り、 ant をインストールした後

$ cd tools/idea
$ ant

とすれば後はビルド終了を待つのみ。

自分の Macbook では 20 分ほどかかりました。ポケモンピクロスでもして待っていましょう。

成果物

tools/idea/out/artifacts 以下にビルドした AndroidStudio 達が存在しています。Mac でビルドしましたが Windows / X Window 用も作られていました。

$ ls out/artifacts
android-studio-SNAPSHOT-no-jdk.mac.zip
android-studio-SNAPSHOT-no-jdk.tar.gz
android-studio-SNAPSHOT-no-jdk.win.zip
core
intellij-core-AI-145.SNAPSHOT.zip
jps
sources.zip

ここから android-studio-SNAPSHOT-no-jdk.mac.zip を解凍すれば Android Studio.app が生成されます。こうして自分専用の Android Studio が使えるようになりました。

JDK

ところでファイル名を見ると no-jdk となっており、Idea JDK が含まれていないようです。なくても困ることはないですが、推奨されているのだから使いたい。

どうやら prebuilts/studio/jdk に然るべき JDK を置けば一緒に含めてくれるみたいです。

ということで、repo のレポジトリ管理ファイルである manifest.xml に新しいレポジトリを追加します。

ローカルの manifest.repo/local_manifests に置けば良いので、.repo/local_manifests/manifest.xml

<?xml version="1.0" encoding="UTF-8"?>
<manifest>

  <remote  name="aosp"
           fetch=".."
           review="https://android-review.googlesource.com/" />

  <project path="prebuilts/studio/jdk" name="platform/prebuilts/studio/jdk" clone-depth="1" revision="studio-master-dev"/>

</manifest>

と記述して repo sync します。

すると prebuilts/studio/jdkplatform/prebuilts/studio/jdk (https://android.googlesource.com/platform/prebuilts/studio/jdk/) の studio-master-dev ブランチが clone されるので、再ビルドすると

$ ls out/artifacts
android-studio-SNAPSHOT-no-jdk.mac.zip
android-studio-SNAPSHOT-no-jdk.tar.gz
android-studio-SNAPSHOT-no-jdk.win.zip
android-studio-SNAPSHOT.mac.zip
android-studio-SNAPSHOT.tar.gz
android-studio-SNAPSHOT.win.zip
android-studio-SNAPSHOT.win32.zip
core
intellij-core-AI-145.SNAPSHOT.zip
jps
sources.zip

が生成されました。

しかし、実行してみると起動してくれない…。ブランチが違ったりしてるし諦めて JDK 無しでやりました。

その後

実行できたので早速 git のログを漁り初めたのですが、それっぽいログがない。実際にソースを見てみてもそれっぽい部分がないので困っていると、ちょうどそれらしきプラグインを発見。*3

github.com

どうやら問題は IntelliJJDK を Idea JDK (8) に切り替えたことが原因のようです。

https://youtrack.jetbrains.com/issue/IDEA-149601

...ということでプラグインを入れることで問題が解決してしまった🙏。*4

ビルドが終わった頃には studio ディレクトリは 12GB も食っていました。残り容量はなんと 500MB になり、警告も出ています。

よって、速やかに studio を削除してこの話は終わりとします。ありがとうございました。

$ df -h 
Filesystem      Size  Used Avail Use% Mounted on
/dev/disk1      186G  185G  529M 100% /
/dev/disk0s4     47G   27G   21G  57% /Volumes/BOOTCAMP
$ rm -rf studio
rm -rf studio  0.81s user 20.83s system 60% cpu 35.700 total
$  df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/disk1      186G  173G   13G  94% /
/dev/disk0s4     47G   27G   21G  57% /Volumes/BOOTCAMP

余談

  • Cross Compile...?

Windows 用はどうやって作っているのだろうと見たところ、ある程度の exe は初めから置いておいて、最後は気合の実装で作るという感じでした。すごい。他のプラットフォームも割りとそういう感じ。

intellij-community/tools/launcher-generator/src/com/pme/exe at master · JetBrains/intellij-community · GitHub

ということは何か将来個人的な変更を加えたい場合は、 CI に投げて自動的に MacWindows 用の AndroidStudio を作るといったことができそう。

  • 容量問題

ソースコードを見たいだけなら https://android.googlesource.com/platform/ から見たいものを clone するだけで良いです。ビルドにはいろいろ必要ですが、見るべきなのは一つのリポジトリ程度なので。

また、intellij 本家を GitHub で見るのもオススメです。

github.com

明日

明日(今日)は id:tyage さんです!!!楽しみですね!!!

*1:使わなければ一つ一つレポジトリを clone していくことになりめんどくさい

*2:depth を付けないと 8GB ほど埋まるので注意

*3:偶然にもこの方の IntelliJ 用 Molokai テーマを弄ったものを使っていた

*4:まだ試してませんが

curl: (60) SSL certificate problem: certificate is not yet valid

対処

時刻がおかしいので ntp とか入れる

経緯とか

久しぶりに VirtualBox で入れていた Arch Linux を起動して、 bundler が入っていなかったので gem install bundler を打つと、

certificate verify failed

とのエラーが *1

確かに curl https://rubygems.org しても上手くいかないし、 curl http://google.com も無理。でも github は上手くいくから何故だろうと思っていた。

$ curl https://google.com
curl: (60) SSL certificate problem: certificate is not yet valid

と言った感じ。(ここでちゃんとログを見ればすぐ解決してた...)

ルート証明書が古かったりおかしかったりするのかなと思ったけど /etc/ssl にちゃんとあるし、変ですねと思ってもう一回ログを見たら "not yet valid" って言ってる。

もしかして時間・・・?と思って date したら内部の日付が一ヶ月半前だった。そりゃダメだ。

Arch, デフォルトでは ntp で同期してないんですね。

結局

# pacman -S ntp
# systemctl enable ntpd.service
# systemctl start ntpd.service

したら直った。

追記

Systemd にそもそも組み込まれていたのでそっちを ON にすればいいという指摘をもらった。ということでそっちを使うように。

# pacman -R ntp
# timedatectl set-ntp false
# timedatectl set-ntp true

一度 false にしないと、 NTP synchronizedno のままだった。

*1:詳しいログまでは記憶してない

Kotlin の String#split で正規表現を用いる

Kotlin の String#split は、 String を引数に取った場合 Regex を使用しません。 正規表現を用いて split を行うには、Regex を引数に取る必要があります。

java

"hogehoge piyo  foo".split("\\s+")
// => ["hogehoge", "piyo", "foo"]

kotlin

  • Bad
"hogehoge piyo  foo".split("\\s+")
// => ["hogehoge piyo  foo"]
  • Good
"hogehoge piyo  foo".split(Regex("\\s+"))
// => ["hogehoge", "piyo", "foo"]

Android の df コマンドについて

最近 Android 用の mackerel-agent を作ろうとしていて、Android/proc/meminfo を読んだりしていたのですが、 filesystem の metrics は df -P コマンドを使っていました。*1

Android にも df コマンドは存在していたのでパースしようとしたのですが、バージョンごとの違いなどがあったのでメモ。

バージョンによる挙動

Andorid 6.0.1 (Marshmallow) まで

$ adb shell
shell@SO-02H:/ $ df
Filesystem               Size     Used     Free   Blksize
/dev                   889.4M   104.0K   889.3M   4096
/sys/fs/cgroup         889.4M     0.0K   889.4M   4096
/sys/fs/cgroup/memory: Permission denied
/mnt                   889.4M     0.0K   889.4M   4096
/tmp                   889.4M     4.0K   889.4M   4096
/system                  4.9G     3.9G     1.1G   4096
/data                   21.9G    12.1G     9.8G   4096
/oem                   503.7M   340.1M   163.6M   4096
/cache                 341.0M   304.0K   340.7M   4096
/rca                     8.4M     6.4M     1.9M   1024
/idd                    14.0M     4.9M     9.0M   1024
/persist                27.5M   804.0K    26.7M   4096
/firmware               80.0M    56.5M    23.5M   16384
/lta-label              11.7M     1.1M    10.6M   4096
/storage               889.4M     0.0K   889.4M   4096
/mnt/runtime/default/emulated: Permission denied
/storage/emulated       21.9G    12.1G     9.8G   4096
/mnt/runtime/read/emulated: Permission denied
/mnt/runtime/write/emulated: Permission denied

Android 6.0 までの df コマンドは coreutilsdf とは大きく異なります。

マウントされた先しか見ることが出来ない他、 -h などのオプションは一切使えません。

$ adb shell
shell@SO-02H:/ $ df -h
Filesystem               Size     Used     Free   Blksize
-h: No such file or directory

Android 7.0 (Nougat) 以降? *2

$ adb shell
bullhead:/ $ df
Filesystem                                             1K-blocks     Used Available Use% Mounted on
tmpfs                                                     922680      428    922252   1% /dev
tmpfs                                                     922680        0    922680   0% /mnt
/dev/block/dm-0                                          2999516  2430552    552580  82% /system
/dev/block/dm-1                                           241908   184160     52752  78% /vendor
/dev/block/platform/soc.0/f9824900.sdhci/by-name/cache     92656     4504     86188   5% /cache
/dev/block/platform/soc.0/f9824900.sdhci/by-name/modem     88016    56720     31296  65% /firmware
/dev/block/dm-2                                         26094648 12222956  13855308  47% /data
/dev/fuse                                               26094648 12222956  13855308  47% /storage/emulated

Android 7.0 では coreutils と同じ形式になりました。

-h-P などのオプションも使えるようになり便利。

$ adb shell
bullhead:/ $ df -h
Filesystem                                             Size  Used Avail Use% Mounted on
tmpfs                                                  901M  428K  901M   1% /dev
tmpfs                                                  901M     0  901M   0% /mnt
/dev/block/dm-0                                        2.8G  2.3G  540M  82% /system
/dev/block/dm-1                                        236M  180M   52M  78% /vendor
/dev/block/platform/soc.0/f9824900.sdhci/by-name/cache  90M  4.3M   84M   5% /cache
/dev/block/platform/soc.0/f9824900.sdhci/by-name/modem  86M   55M   31M  65% /firmware
tmpfs                                                  901M     0  901M   0% /storage
/dev/block/dm-2                                         25G   12G   13G  47% /data
/dev/fuse                                               25G   12G   13G  47% /storage/emulated

細かい話

  • Android 6.0 までの実装は以下にあり、Android toolbox に含まれていました。/proc/mounts から読み取っていますが特にオプションを受け取るなどは行っていません。

platform_system_core/df.c at marshmallow-release · android/platform_system_core · GitHub

  • Android 7.0 からは df の実装が toybox に置き換わったので、POSIX に準拠するようになりました。

Lose df to toybox. · android/platform_system_core@cca6019 · GitHub

toybox とは linux コマンド群の BSD ライセンス実装です*3

おまけ

toybox 自体は 6.0 から Android に取り込まれているので、toybox を使えば 6.0 でも新しい df を使えました。

$ adb shell
shell@SO-02H:/ $ toybox df
Filesystem      1K-blocks       Used  Available Use% Mounted on
tmpfs              910748        104     910644   1% /dev
none               910748          0     910748   0% /sys/fs/cgroup
tmpfs              910748          0     910748   0% /mnt
tmpfs              910748          4     910744   1% /tmp
/dev/block/dm-0   5174428    4054116    1103928  79% /system
/dev/block/bootdevice/by-name/userdata 22994780   12698312   10280084  56% /data
/dev/block/bootdevice/by-name/oem 515776     348284     156844  69% /oem
/dev/block/bootdevice/by-name/cache 349136        304     341624   1% /cache
/dev/block/bootdevice/by-name/diag 14327       5165       8835  37% /idd
/dev/block/bootdevice/by-name/persist 28144        804      26688   3% /persist
/dev/block/bootdevice/by-name/modem 81872      57824      24048  71% /firmware
/dev/block/bootdevice/by-name/LTALabel 12016       1168      10524  10% /lta-label
/dev/fuse        22994780   12698312   10280084  56% /storage/emulated

ただ、toybox のバージョンが少し古いので長い filesystem に対応できずはみ出しています。*4

ということで、今回は Android 6.0 以前と Android 7.0 以降でパースを分けるという対応になりそうです。toybox のおかげで色々コマンド打てるようになって便利。

*1:https://github.com/mackerelio/mackerel-agent/blob/master/util/filesystem.go

*2:未来のことはわからない

*3:busybox は GPL2 なので Android のライセンス上含められなかったそう

*4:ここで修正されている https://github.com/landley/toybox/commit/c10638d3b16d065c1efd97ae17c1a8bf417be706