mac から limimobiledevice を用いて iPhoneの温度を観測してみた件


株式会社サムザップで SRE をしているれおすけです。[サムザップ AdventCalendar 2022](https://qiita.com/advent-calendar/2022/sumzap) の6日目の記事です。
昨日は @ohbashunsuke さんの[【知らないと損する】デザイナー向け5つのUnityテクニック](https://shibuya24.info/entry/unity-basis-designer-technique)でした。

# 背景
スマートフォンでのアプリ開発をしていると何かと気になるのが端末温度です。
第四世代以降の iPhone/iPad/iPod touch は高温になりすぎた場合に本体を保護する機能が組み込まれており、温度が上限を超えると警告メッセージが表示されます。その状態になると、本体の電源を切って、端末温度が冷えるまで使うことができません。
もともと、カクカクした描写にならないように処理の負荷を考慮したり、パフォーマンスを計測することはやってきています。しかし、一つ一つは負荷のかからない処理でも続けて長く使用することで、端末の温度が上がってしまうケースはあると思います（私自身もスマートフォンでゲームをしていて、端末が熱くなったことが何回もあります。）

そこで、社内で課題として上がっていた iPhone の温度計測を MacOS が動くデスクトップやラップトップ（以降、PCと記載）から観測する方法を実現した話を書こうと思います。
**※端末温度と記載していますが、正確にはデバイスのバッテリ温度を取得しています。**

# 目標
JailBreak なしで iPhone の端末温度を記録して、分析、グラフ化するところまでをゴールとして設定しました。実行時に観測する間隔を設定できるようにすることと、分析、グラフ化しやすい形で記録することが必要になります。


# 作成したスクリプト
```shell
     1	#!/usr/bin/env bash

     2	## for safety
     3	set -u          # undefined variable check
     4	set -e          # interupt at errors
     5	set -o pipefail # interupt at pipe errors

     6	## function
     7	usage () {
     8	  echo "Usage: $COMMAND [-i INTERVAL(seconds)]" 1>&2
     9	}

    10	summarize () {
    11	  FINISH_AT=`date +%s`
    12	  DIFFERENCE=$( echo "${FINISH_AT} - ${START_AT}" | bc -l )

    13	  echo 'Interrupted!'
    14	  echo 'start:' ${START_AT}
    15	  echo 'finish:' ${FINISH_AT}
    16	  echo 'diff:' $DIFFERENCE
    17	  cat "./output/${ID}"* | st --N --mean --median --max --min
    18	}

    19	## options
    20	COMMAND=`basename $0`
    21	INTERVAL=5


    22	while getopts i: OPT
    23	do
    24	  case $OPT in
    25	    "i" ) INTERVAL="$OPTARG" ;;
    26	      * ) usage
    27	          exit 1 ;;
    28	  esac
    29	done


    30	## logging
    31	START_AT=`date +%s`

    32	## analyser
    33	DEVICE_IDS=`idevice_id --list`

    34	trap summarize SIGINT

    35	while true; do
    36	  for ID in "${DEVICE_IDS[@]}"; do
    37	    idevicediagnostics -u ${ID} ioregentry AppleSmartBattery | plutil -p - | grep '"Temperature"' | awk '{print $3}'| tee -a "./output/"`date +"${ID}_%Y%m%d_%H%.txt"`
    38	  done
    39	  sleep $INTERVAL
    40	done

```

# 実行結果
![Screen Shot 2022-12-05 at 15.33.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145853/520c447a-900e-6037-2433-88722433a0c7.png)
デバイスの温度を計測して、Terminal と ファイルへ出力しています

![Screen Shot 2022-12-05 at 15.33.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145853/7d6b7887-1581-62fb-d939-c02b85b0a9b9.png)
プログラムを停止すると動いていた時間(開始、終了、差分)とこれまでその端末で観測した値（これまでの実行全部）の分析（サンプル数、最小値、中央値、最大値、平均値）を出力します。

![Screen Shot 2022-12-05 at 15.37.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145853/2d2efb3e-c9cc-bc68-c2dd-ff58946f3b3c.png)
出力された値を gnuplot で描写してみました

# 工夫した点 
- プログラムをできるだけ書かずにコマンドを組み合わせて作り、応用物をどんどん作り出せるように簡易的に済ませました。分析は `st` コマンドで、グラフ描写は `gnuplot`、iPhone からの情報取得は `libimobiledevice`というライブラリのコマンド`idevicediagnostics`を用いました。
- 実行間隔をオプションで指定できるようにして、出力量を調整できるようにしました。コマンド実行して、スリープという形をとっているので、あくまでも実行間隔であり、N秒に1回計測という形ではないところに注意が必要です。私の環境では、0.4 を指定すると大体1秒に1回計測していました。
- 停止した時にも簡易的に統計情報を出力するように `SIGINT` を trap して実行してみました。フォアグラウンドプロセスとして動かすので、停止する時には `Ctrl + C` で停止する必要があります。他のシグナルも trap することを考えたのですが、シンプルに `SIGINT` のみにしました。
- 出力ファイル名に端末IDと日時(`YYYY年MM月DD日HH時`)を用いました。`st` コマンドはファイルに記載された数値を読み取リます。この時ファイルは複数指定可能です。`[DEVICE_ID]_YYYYMMDD_HH.txt`というファイル名に設定すれば、`[DEVICE_ID]_YYYYMMDD_HH.txt`と指定すれば1時間単位の統計情報が簡単に表示することができます。
`[DEVICE_ID]_YYYYMMDD_*` と指定すれば日単位、`[DEVICE_ID]_YYYYMM*`と指定すれば月単位、`[DEVICE_ID]_*`で指定すれば今までの実行全てでの統計になります。

## st コマンドについて
簡単な統計を出力してくれます。実行速度が速く、MacOS には標準で `perl` がインストールされているので手間なく導入できるため、採用しました。
使用しているのは、サンプル数、最小値、最大値、中央値、平均値ですが、他に標準偏差や各種四分位（quartile）、百分位（percentile）などが使用できます。

## libimobiledevice について
iOS上の Service に対して Native Protocol を用いて、デバイスが管理している情報を取得することができます。 

`idevicediagnostics` コマンドで `AppleSmartBattery` というI/O Registory のエントリーを読み込んでいます。`idevicediagnostics` コマンドには端末の UUID をオプションで指定する必要があります。それは `idevice_id --list` を実行して取得します。以下の説明では `${DEVICE_ID}`に UUID が設定されているとします。

```shell
 idevicediagnostics -u ${DEVICE_ID} ioregentry AppleSmartBattery
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IORegistry</key>
	<dict>
		<key>AbsoluteCapacity</key>
		<integer>1127</integer>
		<key>AdapterDetails</key>
		<dict>
			<key>AdapterID</key>
			<integer>0</integer>
			<key>AdapterVoltage</key>
			<integer>5000</integer>
			<key>Current</key>
			<integer>500</integer>
...(以下、省略)
```

plist形式では扱いづらいので、putil コマンドで書式を変換しています。
**※なぜか json にうまく変換できなかったので、簡易的な表示用変換後に grep で抜き出し、awk で切り出しています。**

```shell
 idevicediagnostics -u ${DEVICE_ID} ioregentry AppleSmartBattery | plutil -p -
{
  "IORegistry" => {
    "AbsoluteCapacity" => 1212
    "AdapterDetails" => {
```

I/O Registory の Entry を確認数方法が分からなかったので、私がどう調べたかを書きます。

MacOS用の IO Registory Explorer で該当する項目を探し、iOS でも使用できるかコマンドを実際に実行して確認しながら進めました。IO Registory Explorer は XCode の Additional Tools に含まれているので XCode とは別にインストールが必要になります。
**※どこかに I/O Registory の Entry 一覧があれば教えてほしいです。**

![Screen Shot 2022-12-05 at 17.07.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145853/a4e0780b-20e0-c3a9-6f98-15367de43cdb.png)
AppleSmartBattery の Property を確認し値を確認



# まとめと感想
`libimobiledevice`のおかげでコマンドだけで完結できたのがとても良かったなと思います。また、今後の課題として以下の点があり、改善していく予定です。
1. 現在は出力のフォーマットが適当に値の記録だけおこなっているので、記録日時を記載して後からグラフ化した時にわかりやすいようにしたい
1. `libimobiledevice`が Cross-Platform対応なので、Windows でも実行できるように WSLでの検証を進めたい
1. iPhone7以前が動作保証端末になるプロジェクトもあるかもしれないので対応したい
1. putil で json 変換できず応用しづらいので、変換できなかった理由などを調べて改善したい

久しぶりに ユニケージ開発（UNIX系OSにおいてコマンドとシェルスクリプトでシステムを開発する手法）もどきでツールを作成しましたが、とても楽しかったです。また、iOS の IORegistory 周りを調べるのが初めてだったのでとても苦労しましたが、短時間で作成することができて良かったです。
今後も課題をサクッと解決するツールを作って提供し、プロジェクトや会社の課題を解決していきたいと思います！

[サムザップ AdventCalendar 2022](https://qiita.com/advent-calendar/2022/sumzap) の明日の記事はは [@RyotoKitajima](https://qiita.com/RyotoKitajima)さん担当です。

# 参考文献
- [iPhone、iPad、iPod touch が高温または低温になりすぎた場合](https://support.apple.com/ja-jp/HT201678)
- [gnuplot 公式サイト](http://www.gnuplot.info/)
- [github libimobiledevice](https://github.com/libimobiledevice/libimobiledevice)
- [github st](https://github.com/nferraz/st)
- [Additional Tools for XCode](https://developer.apple.com/download/all/?q=io)
- [I/OKit](https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/TheRegistry/TheRegistry.html)