word で、本来 "1. 序論 1.1 背景 現在、日本の多くの行政区において...... "と表示されるところが、"1."、"1.1 "が表示されなくなりました。 ナビゲーションだけでなく、本文にも表示されていません
見出し1, 見出し2、が全部表示されなくなったが、これで直った。
心臓に悪いから、こういうのは止めて欲しい。
江端智一のホームページ
見出し1, 見出し2、が全部表示されなくなったが、これで直った。
心臓に悪いから、こういうのは止めて欲しい。
以前、
に記載しましたが、SDカードのサイズがほんの少しだけ小さいだけ、という理由で、ラズパイのSDカードのクローンを作るのに失敗したお話をしました。
で、あの時は、SDカード→SDカードの場合に成功したのですが、今回は、ハードディスクに格納したimgファイル→SDカードのケースで対応できませんでした。
.img
ファイルは「SDカード全体の構造」を再現する形式なので、たとえ未使用部分があってもその全体サイズ分を丸ごと書き込む必要があります。そのため、数百MBでも足りないと完全に書き込めません。
そこで、今回は、この対応を試みて、1日がかりで成功しました。128GBのimgファイルのコピーって、もの凄く時間がかかる(2時間以上)ので、そっちでも苦労しました。
が、この方法で、なんとか成功できたようなので、自分用にメモを残しておきます(抜粋のみ)。
img
ファイルを縮小する(上級者向け)pishrink.sh
を使うこれにより、未使用領域を削除して圧縮済み .img
を作成できます。これで書き込み先のSDカードがそれよりわずかに小さい場合にも書き込めるようになります。
※ Windows単体では難しいですが、WSLやUbuntu仮想マシンがあるなら対応可能です。
私は、WindowsのHDDにimgファイルもってきて、WSLから実施しました。
ただ、pishrink.sh
はダウンロードする必要があります。https://github.com/Drewsif/PiShrink/blob/master/pishrink.sh
また、その途中でparted
のインストールも必要になりました。
sudo apt update
sudo apt install parted
こうして、
sudo ./pishrink.sh shajyou_6001_20250221.img shajyou_6001_20250221_shrink.img
を実施しました
で、その結果以下のようなものが作成されました。
約1/3に圧縮されていますので、128GBのSDカードには軽く入る計算になります。
さて、次に、SDカードへの書き込み(クローンの作成)に入ります
Raspberry Pi Imager をダウンロード・インストール
→ 公式サイト: https://www.raspberrypi.com/software/
SDカードをPCに挿入
Raspberry Pi Imagerを起動し、以下を選択:
「OSを選ぶ」→ 一番下の「カスタムイメージを使う」→ shajyou_6001_20250221_shrink.img
を選択
「ストレージを選ぶ」→ SDカードを選択
「書き込む」をクリック → 書き込み完了まで待つ
書き込み→検証で1時間くらいはかかるので、諦めて待ってください。
-----
最後に、このクローンのSDカードで、ラズパイが立ち上がるのかを確認するのですが、かなりドキドキします。
私の場合、初回のブート時に、
てな画面がでてきて、自動的に再起動がかかりましたが、2度目の起動でログイン画面になり、ホッとしました。
最初は、USB関連を抜いて追いた方が良いかもしれません(キーボードとマウスはしょうがないとしても)。
以上
P.S. さて、これからあちこち(PCやらNASやら)にコピーしたimgファイルの削除です。imgファイルだけで128GBの容量を取られるので、本当にシャレになりません。
fastapiからC言語で作成したプログラムを起動すると、Cプログラムの中にあるスレッドが途中で停止するという問題が発生している。
Cプログラム をfastapiプログラムから起動したの場合、udpマルチキャストの受信を⾏うスレッドが、数回〜10数回⽬で停⽌することが発覚。
fastapiプログラムから Cプログラムを起動せず、コマンド(手入力)で起動した場合、この問題は発⽣しないことは確認済。
また、1のCプログラム起動だけでなく、複数のCプログラムを起動した場合においても、この問題が発生しないことも確認済み。
以上より、この問題は、Cプログラム本体ではなくて、それを起動するfastapi(fastapiプログラム)側に問題があると推認された。
この問題は、以下のfastapiプログラムのコードの差し替えによって解決することが判明している。
問題が発生するケースでのfastapiプログラムのコマンド発生プロセスのコード
(1)
pid = await run_command_and_get_pid(command)
(2)
task = asyncio.create_task(run_command_async(command))
process = await task
pid = process.pid
# なお、上記(1)(2)は、実質的には同じ処理である
問題が発生しないケースでのfastapiプログラムのコマンド発生プロセスのコード
with open(os.devnull, 'w') as devnull:
process = subprocess.Popen(shlex.split(command), stdout=devnull,
stderr=devnull)
pid = process.pid
この問題の発生/不発生は、以下の2点に集約できることが確認された。
asyncio.create_task
と await
を併用した非同期処理subprocess.Popen
を使用したプロセス処理Cプログラムは、映像転送を行うメインルーチンと、SRT転送の状況を常時把握するスレッド、自分と自分以外のCプログラムのプロセスをリアルタイムでカウントするスレッドと、稼動中のCプログラムに通信を行うためのスレッド(現在未使用)からなる、映像転送&プロトコル変換プログラムである。
1Mbps以上の映像転送を行いながら、映像転送環境を常に把握し続ける必要のある、リアルタイム性能が要求される高負荷の制御プログラムである。
問題の原因を分析するためには、いくつかの可能性を考慮する必要がある。特に、asyncio.create_task
や await
を使用した非同期処理と、subprocess.Popen
を使用したプロセス管理の違いが影響している可能性がある。この点を中心に考察する。
asyncio.create_task
と await
を使用した場合、非同期処理によってプロセスの状態が意図しない形で処理されている可能性があるが、今回はこれには該当していない。プロセス自体が稼動していることは確認している。したがって、Python のガベージコレクションやスケジューリングの問題によって、起動したプロセスが適切に管理されないケースは考慮しなくてよい。
ログファイルを用いて確認した結果、asyncio.create_task
を使用して作成されたタスクが適切に監視されず終了した形跡はないので、この件について考慮しなくてよい。
非同期関数内で await
を使用してプロセスの終了を待っていると、FastAPI のリクエストが別の処理と競合し、プロセスの動作に影響を与える可能性はあるが、今回はCプログラムが1個の場合でも発生しているので、これも考慮する必要はない。
asyncio
を使用すると、イベントループによってすべての非同期タスクがスケジュールされる。しかし、他のタスクやリクエスト処理が増えると、イベントループ全体の負荷が高まり、処理が遅延する可能性がある。
つまり、fastapiプログラムが、Cプログラムを含めた全体のタスク管理を行うため、Cプログラムの自由な実行が制限されるという問題が発生しうる。
この状況では、特定のタスク(今回の場合はsend_bitrate_or_count_processes
のスレッド)への割り当てが遅れることで、スレッドが停止したかのような挙動が発生することがありえる。
非同期タスクでは、プロセス管理がイベントループの中で行われるため、イベントループ自体の負荷やスケジュールの影響を受けやすくなる。
つまり、リアルタイム性の高い高タスクのCプログラムの実行が、fastapiプログラムの動作を遅らせ、その結果、fastapiプログラムの管理下にあるCプログラムの実行が制限されるという悪循環を発生させると考えうる。
非同期処理では、プロセスの監視 (monitor_process_status
) を含むさまざまなタスクが並行して実行される。この際、イベントループがプロセスの管理や状態確認の処理を後回しにすることで、プロセスの実行状態が適切に反映されず、スレッドが停止しているかのような挙動になったと考えうる。
subprocess.Popen
を使用して起動する場合は、Python の非同期処理機構に依存されることなく(fastapiプログラムの管理に組込まれないため)、独立してプロセスが管理される。このため、プロセスの状態が安定して保たれたと考える。
これは事実上、手入力でコマンドを押下する処理と同じことをやっており、前述の「コマンド(手入力)で起動した場合、この問題は発生しないことが確認済」という内容と整合が取れている。
今回のプログラムにおける問題の原因は、非同期タスクがイベントループのスケジューリングに依存している点に起因していると考えられる。
具体的には、非同期処理 (asyncio.create_task
+ await
) のリソース競合やスケジューリングのタイミングによって、プロセスの管理や監視が適切に行われず、スレッドが途中で停止しているように見えたと考えられる。
以上より、subprocess.Popen
の利用によってCプログラムのスレッド問題が解決したことは、合理的に説明ができる。
以上
非同期タスク (asyncio.create_task
+ await
) の意義は、主に以下の3つの観点に集約される
非同期タスクは、待機中のリソース(たとえば、ネットワークI/OやファイルI/Oなど)を効率的に使用しながら、他のタスクの実行を並行して進めることを可能にする。これにより、CPUの使用率を高め、多くのリクエストやタスクを同時に処理することができる。例えば、クライアントからのHTTPリクエストに対する応答待ちの間に、他のタスクの処理を進めることができるため、高いスループットが実現される
従来の同期的な実装では、一つの処理が完了するまで他の処理を待たせる必要がある(ブロッキング)。これに対して、非同期処理では、あるタスクがI/O待機などで停止している間にも、他のタスクが進行するため、アプリケーション全体のレスポンスが向上する。具体的には、ネットワークのレスポンスを待っている間に他の処理を進行させることで、CPUのアイドル時間を減らし、待機時間のロスを最小化する。
非同期タスクの仕組みは、大量の接続やリクエストを効率的に処理するために設計されている。特に、Webアプリケーションやマイクロサービスでは、多くのクライアントからの接続を管理し、レスポンスを返す必要がある。例えば、FastAPI などの非同期対応Webフレームワークでは、同時に多数のリクエストを処理し、レスポンスを返すことが求められる。このとき、asyncio.create_task
と await
を利用することで、各リクエストを非同期タスクとして並列に処理できる。
FastAPIを使用してリアルタイムパフォーマンスが要求される制御プログラム(Cプログラム)を起動することは、設計上の選択としていくつかの利点と課題が存在すると考えた。
FastAPIはPythonの非同期処理を活かして、高速かつ効率的にリクエストを処理できる。これにより、複数のリクエストを並列に処理することが可能であり、APIベースのコントロールが求められる場面では有効である。
RESTful APIを簡単に構築できるため、外部からのコントロールやモニタリングが要求される制御プログラムにとっては適切である。また、外部から簡単にステータス確認やプロセスの制御が可能になる
FastAPIは、UvicornやGunicornといったASGIサーバーと組み合わせることで、スケーラブルな構成をとることができる。リクエストの多い状況や複数の制御要求が同時に発生するような環境で柔軟に対応できる。
一方で、リアルタイムのパフォーマンスを要求される制御プログラムにおいて、FastAPIの非同期タスクやイベントループの特性が以下のような問題を引き起こす可能性がある。
FastAPIは非同期フレームワークであるため、複数のタスクが同時に実行されることを前提としている。しかし、リアルタイム制御を要するプログラム(映像転送やプロトコル変換など)の場合、イベントループの負荷が高まると、タスクの遅延やリソース競合が発生しやすくなる。
映像の中継やプロトコル変換を行うプログラムでは、タイミングやデータ転送の精度が重要である。FastAPIを介したプロセスの管理やコマンドの実行は、イベントループに依存するため、Pythonのインタープリタの遅延やスレッド管理の問題がリアルタイム性能を損なうリスクがある。
プロセスの制御や起動部分をFastAPIから分離する本件の方式は、fastapi + 制御プログラム の併用方式としては最適である、と考える。
GStreamerをC言語でフレームレートの設定ができなかったので、色々試していましたが、
caps = gst_caps_from_string("video/x-raw, width=640, height=360,framerate=5/1")は動きません(少なくとも私のコードでは駄目だった)。
これね、GStreamerのコマンドと同じ内容になるように並べなければあらないようです。
gst-launch-1.0 rtspsrc location=rtsp://cam:Cam12345@192.168.0.10/Src/MediaInput/stream_1 latency=0 ! rtph264depay ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw,width=640,height=360 ! videorate ! video/x-raw,framerate=1/1 ! x264enc bitrate=1700 speed-preset=ultrafast tune=zerolatency key-int-max=1 ! mpegtsmux ! srtserversink uri = "srt://192.168.101.10:38091" latency=500
これを忠実にコーディングする必要があるようです。
で、今のコードに入っていなかったのが"video/x-raw,framerate=1/1" です。
なので、capsfilter2 やら caps2 のコードを追加する必要がありました。
という訳で、以下のような追加をすることで動くようになりました。
ーーーーー
GstElement *videoscale, *capsfilter, *videorate, *x264enc, *mpegtsmux, *srtserversink;
GstElement *capsfilter2;
GstCaps *caps;
GstCaps *caps2;
ーーーーー
capsfilter = gst_element_factory_make("capsfilter", "capsfilter");
videorate = gst_element_factory_make("videorate", "videorate");
capsfilter2 = gst_element_factory_make("capsfilter", "capsfilter2");
ーーーーー
if (!pipeline || !rtspsrc || !rtph264depay || !avdec_h264 || !videoconvert || !videoscale ||
!capsfilter || !videorate || !capsfilter2 || !x264enc || !mpegtsmux || !srtserversink) {
g_printerr("Not all elements could be created.\n");
abc_log_message("Not all elements could be created.");
return -1;
}
ーーーーー
caps = gst_caps_from_string("video/x-raw,width=640,height=360");
g_object_set(capsfilter, "caps", caps, NULL);
gst_caps_unref(caps);
caps2 = gst_caps_from_string("video/x-raw,framerate=10/1");
g_object_set(capsfilter2, "caps", caps2, NULL);
gst_caps_unref(caps2);
ーーーーー
gst_bin_add_many(GST_BIN(pipeline), rtspsrc, rtph264depay, avdec_h264, videoconvert, videoscale,
capsfilter, videorate, capsfilter2, x264enc, mpegtsmux, srtserversink, NULL);
if (!gst_element_link_many(rtph264depay, avdec_h264, videoconvert, videoscale, capsfilter, videorate,
capsfilter2, x264enc, mpegtsmux, srtserversink, NULL)) {
g_printerr("Elements could not be linked.\n");
abc_log_message("Elements could not be linked.");
gst_object_unref(pipeline);
return -1;
}
ーーーーー
もっと簡単にできると思ったんだけどなぁ・・・結構、手間かかったなぁ。
一つ一つが、大変納得した内容だったので、私のメモ用に記録しておきます。
Go言語を用いたマルチエージェントシミュレーション(MAS)の利点と欠点について、他の言語(C++, Python, Juliaなど)と比較しながら論じる。
Goはgoroutineを用いた並行処理が特徴であり、エージェントごとにgoroutineを割り当てることで効率的な並列処理が可能である。他の言語ではスレッド管理(C++のstd::threadやPythonのthreading)が必要になるが、Goではシンプルな記述で実装できる。
例:
このようにGoの並行処理はシンプルかつ軽量であり、大量のエージェントを扱うシミュレーションに適している。
Goはシンプルな文法と強力な標準ライブラリを持つため、コードの可読性が高く、開発者間での共有が容易である。
C++ではテンプレートやマルチスレッド処理が複雑になりやすく、PythonではGIL(Global Interpreter Lock)の影響で並列処理が制限される。一方、GoはGILの問題がなく、コードの記述量も比較的少ないため、長期的な開発に向いている。
Goはコンパイルが非常に速く、バイナリを直接実行できるため、デバッグやテストのサイクルを短縮できる。C++のように長時間のコンパイルが不要で、Pythonのようなインタープリタ型の遅延もない。
Goは静的バイナリを生成できるため、Windows, Linux, macOS などの異なる環境での実行が容易である。C++ではコンパイル時にライブラリ依存の問題が生じやすく、Pythonでは環境設定(仮想環境やパッケージ管理)が面倒になりがちだが、Goでは1つのバイナリで解決できる。
GoはもともとGoogleが開発した言語であり、クラウド環境(GCP, AWS)やWebとの親和性が高い。
シミュレーション結果をWeb API経由でリアルタイム表示したり、分散処理の一部をクラウド上で実行する際に、Goの標準ライブラリだけでHTTPサーバを簡単に実装できるのは大きなメリット。
PythonやC++には、最適化された数値計算ライブラリ(NumPy, SciPy, Eigen, OpenMPなど)が豊富にあるのに対し、Goは数値計算や線形代数のライブラリが少ない。
そのため、大量の行列演算や物理シミュレーションを行う場合、Go単体では計算効率が劣る可能性がある。
対策:
gonum
ライブラリを使用する。C++はCUDAやOpenCLを利用してGPUによる並列計算が可能であり、PythonもTensorFlowやCuPyを通じてGPUを活用できる。しかし、Goは公式にはGPUを直接サポートしていないため、CUDAを使う場合はCGO経由でC++のライブラリを呼び出す必要がある。
対策:
gorgonia
(Go向けのニューラルネットワークライブラリ)を利用すると、一部のGPU計算が可能。PythonやC++は、数多くの学術論文や研究事例があり、豊富なサンプルコードが存在する。一方で、Goは学術分野ではあまり使われておらず、論文や研究事例が少ない。
特に、MAS関連のライブラリやフレームワークが未成熟なため、開発者が独自に実装する必要がある。
対策:
Goのガベージコレクション(GC)は自動的にメモリを管理するが、大規模なシミュレーションではGCの影響で予期しないタイミングで一時的な遅延が発生する可能性がある。
C++のように明示的なメモリ管理ができないため、リアルタイム性を求めるシミュレーションでは不利になることがある。
対策:
sync.Pool
を活用してメモリ管理のオーバーヘッドを減らす。Go言語を使ったMASは、並行処理の容易さ・コードのシンプルさ・クラウドとの親和性に優れており、大規模エージェントシミュレーションの実装がしやすい。
しかし、GPUサポートの弱さ・計算ライブラリの不足・研究事例の少なさといった課題もあり、特に高度な数値計算が必要なシミュレーションではPythonやC++の方が適している。
Go言語が適しているケース:
PythonやC++の方が適しているケース:
最適なアプローチ:
Go単体でMASを実装するよりも、計算部分をPython/C++で処理し、Goは並列制御やネットワーク管理に使うというハイブリッドアプローチが有効となる。
昨日、
について記載したんだけど、これ簡単に言うと『goroutineを止めて、サブルーチンにした』というものです。
300万のエージェントが、メインルーチンの情報変更に上手く連動しなくなると、"goroutineの自己破壊命令が届かない"という問題がボロボロでてきたからです。
まあ、それでも、『数十万人分、361万行の移動データを、全部表示して正常終了した』というGo言語の凄さには、相変らず感心しているんですけどね(昨夜、PC回しながら寝たのですが、今朝、全部のオブジェクトが消えて、正常終了していました)。
起動方法
$go run server22-1.go
↓
http://localhost:8080
↓
$go run pm_proxy_single_socket.go
// server22-1.go
/*
このプログラムは、WebSocket を利用したリアルタイム位置情報サーバーである。
- クライアントから送信された位置情報を受信し、処理後にレスポンスを返す。
- 位置情報はチャネルを介して他のクライアントと共有される。
- `sync.Mutex` を使用してスレッド安全な処理を実現。
- WebSocket の接続エラー時には適切にログを出力し、安定した通信を確保。
- HTML を提供し、ブラウザ上で WebSocket 通信を確認可能。
*/
package main
import (
"flag"
"html/template"
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
// var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
var chan2_1 = make(chan GetLoc)
var maxid = 0
var mutex sync.Mutex
func echo2(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
//mutex := new(sync.Mutex)
for {
//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
//_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
//mutex.Lock() // ここに置くとデッドロックしてしまう
gl := new(GetLoc)
err := c.ReadJSON(&gl) // クライアントからのメッセージの受信
mutex.Lock()
// 原因不明の対処処理
if gl.ID == 0 && gl.Lat < 0.01 && gl.Lng < 0.01 {
mutex.Unlock()
break
} else if gl.ID < -1 { // 受理できないメッセージとして返信する
//条件分岐 (変なIDが付与されているメッセージは潰す)
//if (gl.ID > maxid) || (gl.ID < -1) { // 受理できないメッセージとして返信する
gl.ID = -1
gl.Lat = -999
gl.Lng = -999
err2 := c.WriteJSON(gl)
if err2 != nil {
log.Println("write1:", err2)
mutex.Unlock()
break
}
} else { // それ以外は転送する
/*
log.Printf("echo2 after c.WriteJSON(gl) ID:%d", gl.ID)
log.Printf("echo2 after c.WriteJSON(gl) Lat:%f", gl.Lat)
log.Printf("echo2 after c.WriteJSON(gl) Lng:%f", gl.Lng)
*/
if err != nil {
log.Println("read:", err)
mutex.Unlock()
break
}
//fmt.Printf("echo2 before chan2_1 <- *gl\n")
chan2_1 <- *gl
//fmt.Printf("echo2 after chan2_1 <- *gl\n")
//で、ここで受けとる
//gl2 := new(GetLoc)
//fmt.Printf("echo2 before gl2 := <-chan2_1\n")
gl2 := <-chan2_1
maxid = gl2.ID // ID最大値の更新
/*
log.Printf("echo2 after gl2 := <-chan2_1 ID:%d", gl2.ID)
log.Printf("echo2 after gl2 := <-chan2_1 Lat:%f", gl2.Lat)
log.Printf("echo2 after gl2 := <-chan2_1 Lng:%f", gl2.Lng)
*/
//fmt.Printf("echo2 before err2 := c.WriteJSON(gl2)\n")
err2 := c.WriteJSON(gl2)
//fmt.Printf("echo2 after err2 := c.WriteJSON(gl2)\n")
if err2 != nil {
log.Println("write2:", err2)
mutex.Unlock()
break
}
//fmt.Printf("end of echo2\n")
}
mutex.Unlock()
}
}
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
/* ここでロックして待つ */
for {
//fmt.Printf("echo before gl := <-chan2_1\n")
gl := <-chan2_1
//fmt.Printf("echo after gl := <-chan2_1\n")
//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.id = %d\n", gl.ID)
//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lat = %f\n", gl.Lat)
//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lng= %f\n", gl.Lng)
err = c.WriteJSON(gl)
if err != nil {
log.Println("WriteJSON1:", err)
}
//fmt.Printf("echo after err = c.WriteJSON(gl)\n")
//fmt.Printf("echo before err = c.RreadJSON(gl)\n")
gl2 := new(GetLoc)
err2 := c.ReadJSON(&gl2)
//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.id = %d\n", gl2.ID)
//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lat = %f\n", gl2.Lat)
//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lng= %f\n", gl2.Lng)
if err2 != nil {
log.Println("ReadJSON:", err2)
}
// ここからチャネルで返す
//fmt.Printf("echo before chan2_1 <- *gl2 gl2.id = %d\n", gl2.ID)
//fmt.Printf("echo before chan2_1 <- *gl2 gl2.lat = %f\n", gl2.Lat)
//fmt.Printf("echo before chan2_1 <- *gl2 gl2.lng = %f\n", gl2.Lng)
chan2_1 <- *gl2
//fmt.Printf("echo after chan2_1 <- *gl2\n")
//fmt.Printf("end of echo\n")
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func smartphone(w http.ResponseWriter, r *http.Request) {
smartphoneTemplate.Execute(w, "ws://"+r.Host+"/echo2")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo2", echo2) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/", home) // home関数を登録
http.HandleFunc("/smartphone", smartphone) // smartphone関数を登録
log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}
var smartphoneTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script>
function obj(id, lat, lng){
this.id = id;
this.lat = lat;
this.lng = lng;
}
function random(min, max){
return Math.random()*(max-min) + min;
}
// var personal_id;
var lat = 35.654543;
var lng = 139.795534;
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
var personal_id = 0;
///// 起動時のボタン
// disabled属性を削除
document.getElementById("open").removeAttribute("disabled");
document.getElementById("open").style.color = "black";
// disabled属性を設定 (closeボタンを非活性化)
document.getElementById("close").setAttribute("disabled", true);
document.getElementById("close").style.color = "White";
// disabled属性を設定 (sendボタンを非活性化)
document.getElementById("send").setAttribute("disabled", true);
document.getElementById("send").style.color = "White";
document.getElementById("open").onclick = function(evt) {
console.log("document.getElementById open");
// disabled属性を設定 (openボタンを非活性化)
document.getElementById("open").setAttribute("disabled", true);
document.getElementById("open").style.color = "White";
// disabled属性を削除
document.getElementById("send").removeAttribute("disabled");
document.getElementById("send").style.color = "black";
// disabled属性を削除
document.getElementById("close").removeAttribute("disabled");
document.getElementById("close").style.color = "black";
////////// 削除2
// ws = new WebSocket("{{.}}");
////////// 削除2終り
////////// 削除1
//var send_obj = new obj(0, 35.654543,139.795534); // 最初は"0"でエントリ
//console.log("open:send_obj");
//console.log(send_obj.id);
//console.log(send_obj.lat);
//console.log(send_obj.lng);
//var json_obj = JSON.stringify(send_obj);
//ws.send(json_obj);
/////////// 削除1終り
if (ws) {
return false;
}
////////// 追加2
ws = new WebSocket("{{.}}");
////////// 追加2終り
ws.onopen = function(evt) {
print("OPEN");
//ws = new WebSocket("{{.}}");
////////// 追加1
var send_obj = new obj(0, 35.654543,139.795534); // 最初は"0"でエントリ
console.log("open:send_obj");
console.log(send_obj.id);
console.log(send_obj.lat);
console.log(send_obj.lng);
var json_obj = JSON.stringify(send_obj);
ws.send(json_obj);
/////////// 追加1終り
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) { // 受信したメッセージはここに飛んでくる
print("RESPONSE: " + evt.data); // jsonメッセージの内容を表示
// データをJSON形式に変更
var obj = JSON.parse(evt.data);
personal_id = obj.id; // IDの取得(何回も取る必要はないが)
console.log("personal_id");
console.log(personal_id);
if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
console.log("before ws.close()");
ws.close();
console.log("after ws.close()");
}
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
console.log("document.getElementById send");
// disabled属性を設定 (openボタンを非活性化)
document.getElementById("open").setAttribute("disabled", true);
document.getElementById("open").style.color = "White";
// disabled属性を削除
document.getElementById("send").removeAttribute("disabled");
document.getElementById("send").style.color = "black";
// disabled属性を削除
document.getElementById("close").removeAttribute("disabled");
document.getElementById("close").style.color = "black";
if (!ws) {
console.log("return false send");
return false;
}
lat += random(0.5, -0.5) * 0.00001 * 10 * 5;
lng += random(0.5, -0.5) * 0.00002 * 10 * 5
//var send_obj = new obj(personal_id, 35.654543,139.795534); // idでエントリ
var send_obj = new obj(personal_id, lat, lng); // idでエントリ
console.log("send:send_obj");
console.log(send_obj.id);
console.log(send_obj.lat);
console.log(send_obj.lng);
var json_obj = JSON.stringify(send_obj);
ws.send(json_obj);
/*
print("SEND: " + input.value);
ws.send(input.value);
return false;
*/
return false;
};
document.getElementById("close").onclick = function(evt) {
console.log(" document.getElementById close");
// disabled属性を削除
document.getElementById("open").removeAttribute("disabled");
document.getElementById("open").style.color = "black";
// disabled属性を設定 (closeボタンを非活性化)
document.getElementById("close").setAttribute("disabled", true);
document.getElementById("close").style.color = "White";
// disabled属性を設定 (sendボタンを非活性化)
document.getElementById("send").setAttribute("disabled", true);
document.getElementById("send").style.color = "White";
if (!ws) {
return false;
}
var send_obj = new obj(personal_id, 999.9, 999.9); // 最初は"0"でエントリ
console.log("close:send_obj");
console.log(send_obj.id);
console.log(send_obj.lat);
console.log(send_obj.lng);
var json_obj = JSON.stringify(send_obj);
ws.send(json_obj);
//ws.close(); // これはws.onmessageの方で実施
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<!-- <p><input id="input" type="text" value="Hello world!"> -->
<button id="send">Send</button>
<button id="close">Close</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PruneMobile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>
<script src="http://kobore.net/PruneCluster.js"></script> <!-- これ、いずれローカルホストから取れるように換える -->
<link rel="stylesheet" href="http://kobore.net/examples.css"/> <!-- これも、いずれローカルホストから取れるように換える -->
<!-- goのテンプレートのローカルって、どこになるんだろう? -->
</head>
<body>
<div id="map"></div>
<script>
ws = new WebSocket("{{.}}"); // websocketの確立
/*
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
*/
// 引数にはミリ秒を指定。(例:5秒の場合は5000)
function sleep(a){
var dt1 = new Date().getTime();
var dt2 = new Date().getTime();
while (dt2 < dt1 + a){
dt2 = new Date().getTime();
}
return;
}
var map = L.map("map", {
attributionControl: false,
zoomControl: false
}).setView(new L.LatLng(35.36716428833585, 139.62724520774287), 16); // 富岡
//}).setView(new L.LatLng(33.58973407765046, 130.41048227121925), 16); // 中州
//}).setView(new L.LatLng(35.654543, 139.795534), 18); 豊洲
// }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
detectRetina: true,
maxNativeZoom: 18
}).addTo(map);
var leafletView = new PruneClusterForLeaflet(1,1); // (120,20)がデフォルト
ws.onopen = function (event) {
}
var markers = [];
//var helicopterIcon = L.icon({ iconUrl: 'http://sintef-9012.github.io/PruneCluster/examples/helicopter.png', iconSize: [48, 48] });
//var airplaneIcon = L.icon({ iconUrl: 'http://sintef-9012.github.io/PruneCluster/examples/airplane.png', iconSize: [48, 48] });
// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
// データをJSON形式に変更
var obj = JSON.parse(event.data);
console.log("233");
console.log(obj.id);
console.log(obj.lat);
console.log(obj.lng);
if (obj.id == 0){ // idが未登録の場合
console.log("obj.id == 0")
// データをマーカーとして登録
var marker = new PruneCluster.Marker(obj.lat, obj.lng);
// 参考資料 http://embed.plnkr.co/WmtpkEqSDJFuFeuiYP54/
//var marker = new PruneCluster.Marker(obj.lat, obj.lng, {
// //popup: "Bell 206 " + i,
// icon: helicopterIcon
//});
console.log(marker.hashCode);
markers.push(marker);
leafletView.RegisterMarker(marker);
console.log(markers);
console.log(markers.length)
obj.id = marker.hashCode;
//ws.send(marker.hashCode); // テキスト送信
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
} else if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
console.log("Math.abs(obj.lat) > 180.0)")
for (let i = 0; i < markers.length; ++i) {
if (obj.id == markers[i].hashCode){
console.log(i)
console.log(obj.id)
console.log("obj.id == markers[i].hashCode")
//leafletView.RemoveMarkers(markers[obj.id]); // これでは消えてくれません
// 1つのマーカーを消すのに、面倒でも以下の2行が必要
var deleteList = markers.splice(i, 1);
leafletView.RemoveMarkers(deleteList);
// 以下失敗例リスト
//leafletView.RemoveMarkers(markers[i].hashCode); //これはダメ
//leafletView.RemoveMarkers(markers[obj.id],'item');
//leafletView.ProcessView(); // 試しに入れてみる
//leafletView.RemoveMarkers(markers[i-1]);
//leafletView.RemoveMarkers(markers);
break;
}
}
obj.lat = 91.0;
obj.lng = 181.0;
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
} else {
// 位置情報更新
console.log("else")
for (let i = 0; i < markers.length; ++i) {
if (obj.id == markers[i].hashCode){
var ll = markers[i].position;
ll.lat = obj.lat;
ll.lng = obj.lng;
break;
}
}
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
}
}
// 位置情報の更新
window.setInterval(function () {
leafletView.ProcessView(); // 変更が行われたときに呼び出されれなければならない
}, 1000);
// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
//print("CLOSE");
ws = null;
}
map.addLayer(leafletView);
</script>
</body>
</html>
`))
// pm_proxy5_single_socket.go
/*
このプログラムは、CSVファイルに記載された順番にエージェントの位置情報を読み取り、
WebSocket を介してサーバーへ送信・更新する。
サーバへ転送するプログラムは、server22-1.go
- WebSocket 切断時に自動で再接続(最大5回リトライ)
- エージェントが移動した場合のみサーバーへ通知
- `sync.Mutex` を使用してスレッド安全な処理を実現
- 位置情報が 999.0 以上の場合、エージェントを削除
csv情報のサンプル (IDと緯度経度のみ使っている
50044035447,139.629538,35.365357,00:00:00,WALK
50044035447,139.629430,35.365151,00:00:30,WALK
50044035447,139.629321,35.364945,00:01:00,WALK
50044035447,139.629213,35.364738,00:01:30,WALK
50044035447,139.629104,35.364532,00:02:00,WALK
50044035447,139.628996,35.364325,00:02:30,WALK
50044035447,139.628888,35.364119,00:03:00,WALK
50044035447,139.628787,35.363937,00:03:30,WALK
50044035447,139.628742,35.364159,00:04:00,WALK
50044035447,139.628699,35.364375,00:04:30,WALK
50044035447,139.628654,35.364592,00:05:00,WALK
50044035447,139.628533,35.364724,00:05:30,WALK
50044035447,139.628261,35.364691,00:06:00,WALK
50044035447,139.627989,35.364658,00:06:30,WALK
50044035447,139.627716,35.364625,00:07:00,WALK
50044035447,139.627680,35.364620,00:07:30,WALK
50044035447,999.9,999.9,00:08:00,WALK
(50044035447, 999.9, 999.9, 00:08:00, WALK) でエージェントの移動が終了し、サーバからアイコンが消滅する
*/
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"math"
"net/url"
"os"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
)
const (
TopLeftLat = 35.37574882601201
TopLeftLon = 139.61403393574466
BottomRightLat = 35.36163108058289
BottomRightLon = 139.6297897196359
)
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type unmTbl struct {
uniName string
objType string
simNum int
pmNum int
lon float64
lat float64
}
var list = make([]unmTbl, 0)
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
var mutex sync.Mutex
func connectWebSocket() (*websocket.Conn, error) {
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
for i := 0; i < 5; i++ {
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err == nil {
log.Println("Reconnected to WebSocket successfully.")
return c, nil
}
log.Println("WebSocket reconnection attempt failed:", err)
time.Sleep(2 * time.Second)
}
return nil, fmt.Errorf("failed to reconnect WebSocket after multiple attempts")
}
func main() {
file, err := os.Open("agent_track_2018_101_end_and_order.csv")
if err != nil {
panic(err)
}
defer file.Close()
fmt.Println("Opened testtest.csv")
reader := csv.NewReader(file)
var line []string
flag.Parse()
log.SetFlags(0)
c, err := connectWebSocket()
if err != nil {
log.Fatal("WebSocket connection failed:", err)
}
defer c.Close()
for {
time.Sleep(time.Millisecond * 10)
line, err = reader.Read()
if err != nil {
break
}
uniName := line[0]
objType := line[4]
lon, _ := strconv.ParseFloat(line[1], 64)
lat, _ := strconv.ParseFloat(line[2], 64)
flag := 0
for i := range list {
if list[i].uniName == uniName {
old_lat := list[i].lat
old_lon := list[i].lon
list[i].lon = lon
list[i].lat = lat
if math.Abs(list[i].lat-old_lat) > 0.000000001 || math.Abs(list[i].lon-old_lon) > 0.000000001 {
gl := GetLoc{ID: list[i].pmNum, Lat: list[i].lat, Lng: list[i].lon}
mutex.Lock()
err = c.WriteJSON(gl)
if err != nil {
log.Println("write2 error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
gl3 := new(GetLoc)
err = c.ReadJSON(gl3)
if err != nil {
log.Println("read error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
mutex.Unlock()
println("move Object!")
if list[i].lat > 999.0 || list[i].lon > 999.0 {
list = append(list[:i], list[i+1:]...)
println("-----------------------------------------------------------------> delete Object!")
}
}
flag = 1
break
}
}
if flag == 0 {
uniNum := len(list)
ut := unmTbl{uniName: uniName, objType: objType, simNum: uniNum, lat: lat, lon: lon}
gl := new(GetLoc)
gl.ID = 0
gl.Lat = ut.lat
gl.Lng = ut.lon
mutex.Lock()
err := c.WriteJSON(gl)
if err != nil {
log.Println("write1 error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
gl2 := new(GetLoc)
err = c.ReadJSON(gl2)
if err != nil {
log.Println("read error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
mutex.Unlock()
ut.pmNum = gl2.ID
list = append(list, ut)
println("------------>generate Object!")
}
}
}
さすがに3つの案件の同時並列処理は無理 ―― と、泣き言を言いながら、今、本当に泣きそうです。
I'm whining that “it's impossible to handle three projects simultaneously,” and I'm about to cry.
経験的に、こういう時『もっと大変な人が、沢山いる』という考え方をすることは、悪手です。
Empirically, thinking that “there are many people in worse situations” is a bad idea in a situation like this.
他人なんぞ、どーでもいい。
I don't care about other people.
「私が辛い」という状態だけで、必要かつ十分です。
The fact that I am in pain is sufficient.
-----
そういえば、今年の年賀状には、長女の結婚式の写真の下に、自分のコメントとして
Speaking of which, on this year's New Year's card, under the photo of my eldest daughter's wedding, I remember I wrote the following comment
『昨年、"うつ"になりました』
'I became depressed last year.'
と書いたのを思い出しました。
『これでは、長女の結婚で"うつ"になったように読める』と気がついたのは、年賀状をポストに投函した後でした。
It was only after I had posted the New Year's card in the post that I realized that it could be read as if I had become depressed because of my eldest daughter's marriage.
しかし、私を"うつ"にするのは、いつだって、仕事です。
However, it is always work that makes me depressed.
その正体の多くが、動かないプログラムであり、停止するシステムです。
Many are programs that don't work and systems that become stop.
世の中には「頼めない仕事」というものが、あるのです。
There are some jobs that I can't ask someone to do.
bytes-received send-rate-mbps receive-rate-mbps bandwidth-mbps rtt-ms cb 0 0 0 12 100 1706 0 1.128166 0 6.216 43.597 2000 0 3.958326 0 10.008 80.069 1831 0 4.864244 0 69.084 74.593 2000
この表にでてくる、send-rate-mbps receive-rate-mbps bandwidth-mbps の単位は何ですか
表にある send-rate-mbps
、receive-rate-mbps
、bandwidth-mbps
の単位は「Mbps(メガビット毎秒)」 です。
項目 | 意味 | 単位 |
---|---|---|
send-rate-mbps | 送信レート(今使っている帯域) | Mbps(メガビット毎秒) |
receive-rate-mbps | 受信レート(今使っている帯域) | Mbps(メガビット毎秒) |
bandwidth-mbps | 推定利用可能帯域(まだ使える帯域) | Mbps(メガビット毎秒) |
send-rate-mbps
(送信レート)send-rate-mbps = 4.864244
なら、約 4.86 Mbps でデータを送信している。receive-rate-mbps
(受信レート)receive-rate-mbps = 0
なので、受信データはない。bandwidth-mbps
(推定帯域幅)bandwidth-mbps = 69.084
なら、SRTは「この回線では約 69.08 Mbps まで使える」と推定している。1 Mbps(メガビット毎秒)は:
例えば:
send-rate-mbps = 4.86 Mbps
send-rate-mbps
、receive-rate-mbps
、bandwidth-mbps
の単位は「Mbps(メガビット毎秒)」send-rate-mbps
は今使っている送信帯域receive-rate-mbps
は今使っている受信帯域bandwidth-mbps
はSRTが推定した利用可能な最大帯域このデータから、送信レートは 最大 4.86 Mbps であり、ネットワークが 最大 69 Mbps まで利用可能 と推定されていることが分かります。
GStreamerのC言語のライブラリの、res = gst_structure_get_double(stats, "bandwidth-mbps", &bandwidth_mbps); g_print("bandwidth-mbps: %d %f\n", res, bandwidth_mbps);の値が何の値を返しているのか教えて下さい
このプログラムにおける bandwidth_mbps
の値は、SRT(Secure Reliable Transport)通信における推定帯域幅(Mbps) を示していると考えられます。
bandwidth_mbps
の取得と用途bandwidth_mbps
は、以下のコードで取得されています。
この部分では、GStreamer の srtserversink
から取得できる SRTの統計情報 (stats
) から "bandwidth-mbps"
というキーの値を取得しています。
GStreamer の SRTプラグイン (srtserversink
) は、SRTプロトコルのメトリクス情報を提供し、その中に "bandwidth-mbps"
というパラメータが含まれています。
つまり、SRT通信時の「利用可能な推定帯域幅」をリアルタイムで取得している。
bandwidth_mbps
の意味bandwidth_mbps
の用途「SRTの推定帯域幅(Mbps)」というのは、「今使っている帯域なのか」、「まだ使える帯域なのか」どちらでしょうか
bandwidth_mbps
は、SRT (Secure Reliable Transport) における「推定帯域幅 (Estimated Bandwidth)」を示す値であり、その意味合いは 「まだ使える帯域」= 利用可能な推定帯域 に近いものです。
bandwidth_mbps
は「今使っている帯域」ではないプログラム内では、SRTの統計情報 (stats
) から bandwidth-mbps
の他に、send-rate-mbps
や receive-rate-mbps
という値も取得できるようになっています。
send-rate-mbps
→ 現在の送信レート(今実際に使っている帯域)receive-rate-mbps
→ 現在の受信レート(今実際に使っている帯域)bandwidth-mbps
→ SRTプロトコルが推定した、利用可能な最大帯域このことから、bandwidth-mbps
は「現在使っている帯域」ではなく、「ネットワークが許容できる帯域の推定値」 であると考えられます。
bandwidth-mbps
の定義(SRT公式の仕様より)SRTの公式仕様では、"Estimated Bandwidth"(推定帯域)は次のように説明されています。
bandwidth-mbps: The estimated maximum available bandwidth of the connection, calculated by the SRT protocol based on observed network conditions such as packet loss and RTT.
つまり、SRTが「この回線ではこのくらいの帯域が利用できるはずだ」と推定している値 であり、今実際に使用している帯域とは違う ということです。
指標 | 意味 | 役割 |
---|---|---|
send-rate-mbps | 今使っている帯域(送信速度) | 実際の送信速度をモニタリング |
receive-rate-mbps | 今使っている帯域(受信速度) | 実際の受信速度をモニタリング |
bandwidth-mbps | まだ使える帯域(推定値) | SRTが計算した利用可能な最大帯域 |
✅ bandwidth_mbps
は「まだ使える帯域」= 利用可能な推定帯域
✅ SRTがネットワーク状況を解析して算出した、回線が許容できる最大帯域を示している
✅ 「今使っている帯域(現在の送信速度)」ではなく、「どのくらいの帯域が利用可能か」という指標
この値を使って、適切なビットレートを動的に調整し、映像配信を安定させる仕組みになっています。