改めて、ntpd が GPS 時刻を取れなくなった場合に必要なプログラムを再起動する

以前 [GPS モジュールを見失ったら gpsd を再起動させる](https://qiita.com/jp_yen/items/08c4000b764a04fbfe03) で対応しようとしたが上手くいかず暫く放置していたが、改めて考える。

# 考えられる GPS 時刻が取れなくなる原因
1. GPS モジュールと gpsd が通信速度が合わずに通信できない
2. gpsd 再起動後にセマフォを使った ntpd との通信が上手くいかなくなる

# 確認方法
1. GPS モジュールと gpsd 間
a) `ntpshmmon` で該当モジュールの出力があるか確認
b) `gpsmon` で、該当モジュールの出力を確認
`gpsmon localhost:2947:/dev/ttyUSBn`
c) `gpscat` で、該当モジュールの出力を確認
`gpscat localhost:2947:/dev/ttyUSBn`

2. gpsd と ntpd 間<
a) `ntpq -p | grep SHM` で、reach 欄が 0 でない
b) `ntpq -c associations` で、対象の condition と last_event が「reject unreachable」になっていない (対象は `ntpq -c apeer` で assid を確認)

# 解消方法
1. GPS モジュールと gpsd 間<br>
gpsd を再起動
2. gpsd と ntpd 間<br>
ntpd を再起動

# スクリプト化
1. GPS モジュールと gpsd 間

### 検出 (gpsd)
```bash:
# /home/pi/opt/gpsd-3.18.1/ntpshmmon -t 3
ntpshmmon version 1
#      Name Seen@                Clock                Real                 L Prec
sample NTP0 1568759552.858062790 1565059095.067431939 1565059095.000000000 0 -20
sample NTP2 1568759552.858158518 1568759552.001487029 1568759552.000000000 0 -30
sample NTP3 1568759552.858191018 1564716014.348685357 1564716014.000000000 0 -20
sample NTP4 1568759552.858222112 1564716015.000534782 1564716015.000000000 0 -20
sample NTP5 1568759552.858252476 1564716986.066649638 1564716986.000000000 0 -20
sample NTP6 1568759552.858282632 1564716986.996633827 1564716987.000000000 0 -20
sample NTP2 1568759553.001645576 1568759553.001487139 1568759553.000000000 0 -30
sample NTP2 1568759554.001593915 1568759554.001486051 1568759554.000000000 0 -30
sample NTP2 1568759555.001652722 1568759555.001486057 1568759555.000000000 0 -30
```
この 2行目の Name 欄を抜き出して uniq して 6行あれば全ての GPS レシーバと通信できている。

前回も載せたスクリプト

```bash:/root/bin/ntp_semaphore_check
#!/bin/sh

NTPSHMMON=/home/pi/opt/gpsd-3.18.1/ntpshmmon

count=`$NTPSHMMON -t 3 | grep sample | cut --delim=" " -f 2 | sort -u | wc -l`

if [ $count -ne 6 ]; then
        logger -p local0.notice "$0: ntpshm ckeck, error detected."
        echo | /root/bin/gpsd_restart
fi
```
/root/bin/gpsd_restart は gpsd を再起動するスクリプト。

### 再起動 (gpsd)
```bash:
#!/bin/sh

SYSTEMCTL=/bin/systemctl
STTY=/bin/stty
GPSCTL=/home/pi/opt/gpsd-3.18.1/gpsctl
IPCRM=/usr/bin/ipcrm

MKTEMP=/bin/mktemp
LOCKDIR=/tmp/`basename $0`
LOCKPID=$LOCKDIR/pid

# SHM segments
# http://doc.ntp.org/4.2.8/drivers/driver28.html
clear_shm (){
# セマフォを削除します
# 通常、セグメントサイズを変更した場合のみ実行すればよい
# https://github.com/rnorris/gpsd/blob/master/SConstruct#LC1963
# 削除すると key は一緒でも shmid が変わるので ntpd の再起動が必要になる。やっちゃダメ。
#   for i in 0x4e545030 0x4e545031 0x4e545032 0x4e545033 0x4e545034 0x4e545035 0x4e545036 0x4e545037 0x47505344;do
#       $IPCRM -M $i
#   done
}

stty_speed () {
    for i in /dev/ttyS0 /dev/ttyUSB0 /dev/ttyUSB1; do
        $STTY -F $i 115200
    done
}

# already running other process?
if [ -d $LOCKDIR ]
then
    # LOCKDIRがある
    if [ -f $LOCKPID ]
    then
        # PIDファイルがあってそのプロセスがない or ある
        kill -0 `cat $LOCKPID`
        if [ $? -ne 0 ]; then
            rm -rf $LOCKDIR && break
        else
            logger -p local0.notice "$0: still running."
            exit
        fi
    fi
fi

# make lock directory
mkdir $LOCKDIR
if [ $? -ne 0 ]; then
    logger -p local0.notice "$0: lockfile make error."
    exit
fi
echo $$ > $LOCKPID


while read LINE; do
    $SYSTEMCTL stop gpsd
    clear_shm
    /bin/sleep 1
    sync
    $SYSTEMCTL start gpsd
    logger -p local0.notice "$0: gpsd restart to me!"
done

# remove lock directory
rm -rf $LOCKDIR
``` 
2. gpsd と ntpd 間

### 検出 (ntpd)
```bash:
# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+ntp-a3.nict.go. .NICT.           1 u 1375  68m  377   10.311    0.431   1.508
+ntp-b2.nict.go. .NICT.           1 u  66m  68m  377   13.518    1.582   1.541
+ntp-b3.nict.go. .NICT.           1 u 1537  68m  377   13.007    1.783   2.277
*ntp-a2.nict.go. .NICT.           1 u  102  68m  377   12.483    1.370   1.337
-ats1.e-timing.n .PPS.            1 u  60m  68m  273   11.622    0.282   0.987
-cwa225.bai.ne.j 210.171.0.34     4 u  71m  68m  377    6.457    2.216   1.619
-cwa201.bai.ne.j 210.171.0.34     4 u  47m  68m  377    5.647    3.896   5.895
 SHM(0)          .NEMA.           0 l  47d    1    0    0.000  -61.442   0.000
 SHM(2)          .PPS.            0 l  47d    1    0    0.000    0.509   0.000
 SHM(3)          .NEMA.           0 l  47d    1    0    0.000  -75.459   0.000
 SHM(4)          .PPS.            0 l  47d    1    0    0.000   -0.529   0.000
 SHM(5)          .NEMA.           0 l  47d    1    0    0.000  -69.134   0.000
 SHM(6)          .PPS.            0 l  47d    1    0    0.000   -0.257   0.000
```
remote が SHM を含み、reach が 0 の項目があるかチェック。

```bash:
ntpq -p | awk '($0~/SHM/ && $7==0){print "error";exit}'
```

### 再起動 (ntpd)
GPS モジュールの reach が 0 になっていたら gpsd が正常に動いているかを確認し、動いていなければ ntpd の再起動。

```bash:
ntpq -p | awk '($0~/SHM/ && $7==0){print "error";exit 255}' || systemctl restart ntpd
```

# スクリプトをまとめてみる

流れ
1. gpsd が GPS モジュールから時刻を取得できているか？ (ntpshmmon)
→ できている 3
→ できていない 2
2. gpsd を再起動し終了
ntpd でも時刻が取得できていない筈だが、それで通信の可否は判定できない。
3. ntpd が gpsd から時刻を取得できているか？ (ntpq)
→ できている (終了)
4. ntpd を再起動し終了

判断用とログ用でコマンドを 2回起動していたりしますがまぁ、
ただいま書換え中...

```bash:GPS_time_check
#!/bin/bash
# 初期設定
SYSTEMCTL=/bin/systemctl
STTY=/bin/stty
IPCRM=/usr/bin/ipcrm
LOGGER=/usr/bin/logger
TR=/usr/bin/tr
AWK=/home/pi/opt/bin/awk

NTPQ=/home/pi/opt/bin/ntpq
NTPSHMMON=/home/pi/opt/gpsd-3.18.1/ntpshmmon
PYTHONPATH=/home/pi/opt/lib/python2.7/site-packages:/usr/local/lib/python2.7/site-packages:/usr/lib/pypy/lib-python/2.7/site-packages
export PYTHONPATH

GPSTTYS="/dev/ttyS0 /dev/ttyUSB0 /dev/ttyUSB1"

# debug
trap 'echo "[${BASH_SOURCE:-$0}:$LINENO] - "$BASH_COMMAND" abnomal status"' ERR

# 二重起動防止 (ロック)
MKTEMP=/bin/mktemp
LOCKDIR=/tmp/`basename $0`
LOCKPID=$LOCKDIR/pid

if [ -d $LOCKDIR ]; then
	# LOCKDIRがある
	if [ -f $LOCKPID ]; then
		# PIDファイルがあってそのプロセスがない or ある
		kill -0 `cat $LOCKPID`
		if [ $? -ne 0 ]; then
			rm -rf $LOCKDIR
		else
			$LOGGER -p local0.notice "$0: still running."
			exit
		fi
	else
		rmdir $LOCKDIR
	fi
fi

# make lock directory
trap 'rm -rf $LOCKDIR' 1 2 3 15
trap 'rm -rf $LOCKDIR' EXIT
mkdir $LOCKDIR
if [ $? -ne 0 ]; then
	$LOGGER -p local0.notice "$0: lockfile make error."
	exit
fi
echo $$ > $LOCKPID

stty_speed () {
	echo -n $GPSTTYS | xargs -d " " -n 1 -i'{}' $STTY -F '{}' 115200
}

# セマフォを削除
# http://doc.ntp.org/4.2.8/drivers/driver28.html
# セグメントサイズを変更した場合のみ実行すればよい
# https://github.com/rnorris/gpsd/blob/master/SConstruct#LC1963
# 削除すると key は一緒でも shmid が変わるので ntpd の再起動が必要になる。普段はやっちゃダメ。
clear_shm (){
	echo shemapho clear
	for i in 0x4e545030 0x4e545031 0x4e545032 0x4e545033 0x4e545034 0x4e545035 0x4e545036 0x4e545037 0x47505344; do
		$IPCRM -M $i
	done
}

check_gpsd (){
	RESULT="`$NTPSHMMON -t 3 | $TR '\n' @`"
	if [ `echo $RESULT | $TR @ '\n' | $AWK '/NTP[0-9]/{print $2}' | sort -u | wc -l` -ne 6 ]; then
		echo "$NTPSHMMON error detected"
		echo $RESULT | $TR @ '\n'
		echo $RESULT | $TR @ '\n' | $AWK '/NTP[0-9]/{print $2}' | sort -u
		$SYSTEMCTL stop gpsd
		stty_speed
		/bin/sleep 1
		$SYSTEMCTL start gpsd
		$LOGGER -p local0.notice "$0: gpsd restart to me!"
		echo 'gpsd: Restart done!'
		exit 255
	fi
}

check_ntpd (){
	RESULT="`$NTPQ -p | $TR '\n' @`"
	echo "$RESULT" | $TR @ '\n' | $AWK '($0~/SHM/ && $7==0){exit 255}' || \
	( echo "ntpd error detected" ;\
	  echo "$RESULT" | $TR @ '\n' | $AWK '($0~/SHM/ && $7==0){print}' ;\
	  $SYSTEMCTL restart ntp ;\
	  $LOGGER -p local0.notice "$0: ntpd restart to me!";\
	  exit 255 )
}

check_gpsd && check_ntpd
```
定期的に起動してチェックする

```crontab:crontab
*/10 * * * * /root/bin/gps_time_check
```

# rsyslog で ntp 関連のログを別ファイルに保存する

```conf:/etc/rsyslog.conf
# ntpd & gpsd logging
# 出力の書式
template (name="ntp_mess" type="string" string="%timereported% %hostname% %syslogfacility-text%.%syslogseverity-text% %syslogtag% %msg%\n")

# 出力ファイル名
template (name="NtpLogfile" type="string" string="/var/log/ntp.log_%$year%%$month%")

# kernel メッセージで ppp や、usb, ch341 (sub-serial インターフェイス) が含まれていたらロギング
if $syslogfacility-text == 'kern' and (
        re_match($msg,'pps pps[0-9]+:') or ($msg contains "usb") or ($msg contains "ch341")
    )
then {
    action(type="omfile" template="ntp_mess" dynaFile="NtpLogfile")
}

# systemd からのメッセージで gps や ntp という文字列が含まれる場合はロギング
if ($syslogtag contains 'systemd') and ($msg contains_i 'gps' or $msg contains_i 'ntp')
then {
    action(type="omfile" template="ntp_mess" dynaFile="NtpLogfile")
}

# プログラム名に ntpd や gpsd という文字列が含まれる場合はロギング
if ($programname == 'ntpd') or ($programname == 'gpsd')
then {
    action(type="omfile" template="ntp_mess" dynaFile="NtpLogfile")
}

# メッセージ部分に再起動スクリプトのファイル名が含まれていたらロギング
if ($msg contains '/root/bin/gps_time_check')
then {
    action(type="omfile" template="ntp_mess" dynaFile="NtpLogfile")
}
```

# rsyslog の設定を反映させる
```tcsh:
> sudo systemctl restart rsyslog
```

# その後
```tcsh:
> bash -c 'while true; do date; ntpq -p; echo; sleep 10; done'
2019年  9月 22日 日曜日 16:10:26 JST
     remote                                   refid      st t when poll reach   delay   offset   jitter
=======================================================================================================
-ntp-a3.nict.go.jp                       .NICT.           1 u  192  256  377   9.7247   0.7944   4.4669
-ntp-b2.nict.go.jp                       .NICT.           1 u  185  256  377   8.9898   0.2955   1.4627
-ntp-b3.nict.go.jp                       .NICT.           1 u  191  256  377   9.9750   0.9161   0.8876
-ntp-a2.nict.go.jp                       .NICT.           1 u  180  256  377   9.0554   0.4391  21.2595
-61-114-187-55.secomtrust.net            .PPS.            1 u  185  256  377  10.5863   0.6503   1.6526
-cwa225.bai.ne.jp                        210.171.0.34     4 u  193  256  377   2.3816   0.3855   3.1480
-cwa201.bai.ne.jp                        210.171.0.34     4 u  216  256  377   1.3140   0.3483   1.5777
xSHM(0)                                  .NEMA.           0 l    -    1  377   0.0000 -48.3162   0.9223
+SHM(2)                                  .PPS.            0 l    1    1  377   0.0000   0.5475   0.0349
xSHM(3)                                  .NEMA.           0 l    -    1  377   0.0000 -61.3037   1.6150
+SHM(4)                                  .PPS.            0 l    1    1  377   0.0000  -0.2275   0.0460
xSHM(5)                                  .NEMA.           0 l    -    1  377   0.0000 -61.7563  18.4702
*SHM(6)                                  .PPS.            0 l    1    1  377   0.0000  -0.2266   0.0295
```
相変わらず jitter が小さいですねぇ。これで安定するかな？ (いや、再起動してたら安定してないけど)

# ntpviz でグラフ化してみた
GPS を常に見るようになって offset (青) は小さくなりましたね。ただ、pps 信号が毎秒チェックするので Frequency Offsets (赤) の変動が凄い。
![local-offset.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306041/73a1a211-4bde-a9a1-96b2-16db2b00c47d.png)
作成方法：`> ntpviz -d ntpstats/ -n Ras1 -p 31 -o ./htdocs` で html ドキュメントを作成
※Raspberry Pi だと全然終わらない (140分以上) ので Windows Subsystem for Linux で入れた Debian GNU/Linux へログをコピーして実行 (3分)。Debian で gnuplot, NTPsec のインストール済み。

NICT 群とのずれを比較しようとしたけど、GPS を見てない間は更新頻度が低いのか、点線で描かれてしまい読み難くて判断がつかない。いくらか良くなってるかな？
![nict.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306041/434d86c9-d2df-dfaa-45aa-ec6155c45622.png)
作成方法：`> ntpviz --peer-offsets "133.243.238.163,133.243.238.164,133.243.238.243,133.243.238.244" -d ntpstats/ -n Ras1 -p 31 -g > htdocs/nict.png`