2014年4月20日日曜日

データ・ロギング・システムの検討(socket::send関数の戻り値について)

半年ほど前にちょっと検討したことがあります。
その時わかったこととして、socketの処理時間(特に受信時間?)が随分かかるのと、ファイルキャッシュが強力で、受信する方はかなりの容量受信してしまうが、本格的にプログラムを作って動かしたときの挙動が心配なことがわかったところで、そのまま放置していました。(特にまだ切羽詰まった必要性がなかったもんで)

なんとなくsocketまわりのコードを見ていたら、ちょっとsend関数の挙動について気になることがでてきました。
send関数のman見ると、以下の説明がでてきます。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

これまでリターンでは"-1"しか気にしてませんでしたが、よくみると「送信できたバイト数を返す」とあります。
これって”必ずしも要求した送信データをすべて1回のAPIコールでは送信しきれないこともあるよ”っていうことです。これまで経験したことないんですが、ここで考えているようなsocket通信に負荷が高いことをやろうとする場合は考えとかないといけないんじゃない?と気づきました。そこんところ注意するなら、以下のようにコーディングすべきです。

while (送信したいバイト数 > 0) {
  送信済バイト数 = send(  );
  送信したいバイト数 -= 送信済バイト数
}

みたいな感じでwhile-loopを組むべきです。
しかし更に問題として、ここでそもそも「一度に送信できない状況って何?」ということがあります。
このsocketが実装された当時は単純にCPUの処理速度やらまわりのHWの性能が遅くて、途中でも制御を返すようにしていたと考えられますが、現在の様にHWの性能が格段に進歩していて、それでも要求されたバイト数を送りきれない、としたら以下の状況が考えられます。

  1. 送信側のバッファが一杯になった
  2. 上記が起きる原因として、受信側の処理が間に合わなくなりAckを十分返しきれてない

従って、単純にwhile-loopで待ってても大丈夫なのか?ということになります。それにあまりsend()で負荷をかけるとkernelが不機嫌になる(暴走する)ことも起きそうで、そうなるとsend自体がerrorで返されることも考えられます。

そもそもデータ・ロギング・システムを作ろうと考えたとき、ロギングデータの抜けを許すか?ということを頭から考えてませんでした。抜けを許さない状況なら、エラーに対処するコード(sendがerrorを返したときの復旧コード)を考えるだけ無駄です。実際、現状自分が考えている用途は試験データの収集を想定しており、途中でログ・データの抜けがあったら試験失敗になり、再試験となります。従ってエラーがでない限りのロギング可能なデータ量が事前にわかっている必要があります。

とりあえず、sendの戻り値は監視するようにしとかないといけませんね。(現状は、エラーの"-1"しかチェックしないことがほとんどです)
もし送りきれない状況が起きたら、システムとしてロギング可能量をオーバーしたのか見極めないと。

あと、現状Webサーバー等でこの種の大量のデータをSocketで送受信しているシステムだと、CPU負荷の問題でもこのような送信不可の状況が一時的に発生することが考えられます。そういうシステムの場合は、抜けが発生したらそれはいさぎよく諦めて再送信してね、という対応になるでしょうから、上記while-loop内に更にtime-outに関する実装も必要になってくるでしょう。

2014年4月17日木曜日

Mac Book Pro(Retina) sleepから起きるときに暴走する(ことがある)

表題の通りなんですが、今年の1月に購入したMacBook Pro Retina 13'によく外部ディスプレイをthunderboltアダプター経由つないで使っていると、しばらく操作していなくて画面がsleep(AC powerで動かしています)したときに起こそうとすると、暴走することが時々あります。(レインボーのアイコンが回りっぱなし、CPUファンも全力になります。当然、マウスの動きも遅く、何か操作しようにもなかなか反応しなくなります。)
なんとか「アクティビティモニタ」を起動し、動いているプロセスを確認すると、kernelが暴走しているようです。シャットダウンもできません。(がんばって時間をかければメニューから選択できるのかもしれませんが・・・・)

どうにも具合が悪く、色々調べていましたが、自分の状況に近い書き込みを見つけました。症状は、外部ディスプレイを接続時にkernelが暴走することがあるとのことで、原因はInsomniaTというアプリのバグだということです。ただ自分のMacには入れた覚えがなく、何のアプリか見てみると、MacBookProで画面がSleepしても、CPUをSleepさせない(バックグラウンドで動かしているアプリを動かし続ける)ツールとのことです。しかも、kernel-pluginで実装してあるとか・・・ちょっとヤバげなツールです。

しかし自分のにはインストールしてませんし、念のため手動でアンインストールする手順があったので調べても、やはり使ってません。しかし症状は同じ感じなんですよね。
そんなときはApple Hardware Testを使って、ハードウェアに問題がないか確認しろという意見がありました。故障というのも困るので念のためやっておきます。手順は以下の通りです。(Appleのサポートページ内にあります)
→2013年6月以降発売のMacの場合はAHTではなくApple Diagnosticと名称が変わっていました。画面もSSと同じでした。

  1. 電源ボタンを押してコンピュータを起動する。
  2. グレイの起動画面が表示される前に「D」キーを押したままにする。
  3. AHTが起動しハードウェア構成を検査するまでに約1分かかる。検査の実行中はアイコンが画面に表示される。
  4. プロセスが完了したら言語を選択して「return」キーを押す。
  5. AHTコンソールが表示され実施するテストを選択する。
  6. AHTを終了するにはウインドウの下部にある「再起動」もしくは「システム終了」をクリックする。

ただ、自分のMavericksが最初から入ったMacでは3.のところでアイコンがでませんでした。しかもいきなり4.の言語選択画面になり、(スクリーンキャプチャは動かないだろうからデジカメで撮りました。ピンボケですみません。)

(ピントがあってませんが、左上2個目が日本語です)
言語を選択するとテストの実行に入りました。(「検査中」と表示されたんですが、多分テストだよね?)


(約2分という表示で、バーがだんだん伸びていきます)
ちょっと動きが違いますが、とにかく異常はでてきませんでした。

(診断結果で、異常なしとでてきました)

Macのシステム環境設定→省電力のところを確認すると、デフォルトではAC powerで動かしていても画面がSleepするとCPUもSleepしてしまうようです。試しにこれを外し、AC powerのときは画面がSleepしてもCPUはSleepしないようにしてしばらく使ってみます。

2014年4月12日土曜日

Raspberry Pi 監視カメラの実験

昨秋、カメラモジュールを買ってから少し試しただけでそのままにしてましたが、ちょっと監視カメラの実験をしてみました。

やることは単純で、

  1. 静止画撮影コマンドをプログラムで定期的に起動
  2. ffmpegで動画にする

これだけです。

1.プログラム作成
色々試しながらやるのでpythonにて作成してみました。30秒毎に1枚、1時間記録してみます。(最初はキャプチャした時間をファイル名にするようにしてましたが、それだと動画にするのが大変だったので単純にカウンタにしました。)

capture.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import threading
import time
import datetime
import subprocess

# 画像保存エリア
cabstr = 'Cab/'

class test(threading.Thread):
    def __init__(self, idx):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.i = idx

    # captureに10秒くらいかかるので連続呼び出しには注意!
    def run(self):
        # print "Start."
        # 日時を表す文字列の生成
        # filename = datetime.datetime.now().strftime(u'%Y%m%d%H%M%S') + '.jpg'
        # print filename
        # 0埋め、後ろから4文字とる
        filename = ("00000000" + str(self.i))[-4:]
        # カメラでのキャプチャコマンド(640x400)
        cmd = 'raspistill -w 640 -h 400 -o ' + cabstr + filename + '.jpg'
        # cmd = 'ls -F'
        subprocess.call(cmd, shell=True)

if __name__ == "__main__":
#    t = test()

    for loop in range(120):
        t = test( loop )
        t.start()
        time.sleep(30)


24H動かすなら、本当はキャプチャしたファイルを転送することも考えないといけないんですが、そこまで本格的なことはとりあえず考えてないので、Raspberry Pi本体のSDに記録です。(32GBをのせてます)メモリを喰いたくないのと、それほど詳細な画像も必要ないので、640x400でキャプチャしてみました。
ちょっと注意が必要なのが、プログラムでは静止画キャプチャ(スレッド)を30秒毎(正確にはスレッド起動時間分だけだんだん遅れていくんですが)に起動しています。キャプチャにはRaspberry Piが提供しているコマンドをそのまま使っていますが、これが結構時間がかかります。(数秒くらい。下手すると10秒近いこともある。)最初、「5秒間隔で試すか」とやってみたら、なんかmmalエラーとかでてきて意味が分かりませんでした。(ぐぐっても関連するのはヒットしませんでした)
これ、raspistillコマンド実行中に別スレッドが起動して更にraspistillコマンドを実行するとリソースの衝突が起きていることを示しているエラーのようです。スレッドの起動間隔を10秒以上にしたらエラーがでなくなり、うまく動くようになりました。

2.動画にする
ここはffmpegのコマンドで静止画を動画にするだけです。(さすがにこれは別PCのUbuntuでやりました)

$ ffmpeg -r 2 -i %04d.jpg -vcodec mjpeg  out.mp4

(Ubuntuでは最近"avconv"に変わったとも聞きましたが、両方入ってました。2枚/秒のレートで動画にしてみました。(1時間分を1分の動画に短縮することになります)「-vcodec」オプションが怪しい気がしますが、とりあえずこれで変換できたようです。)


3.おまけ
今回はリモートログインしたままで実行しましたが、実際に長時間監視するときはそうもいきません。バックグラウンドで動かして、ログアウトしたくなります。そんなときは以下のようになります。

$ nohup ./capture.py &