uWSGIでStatsが見られるようにしたい

# TL;DR
* uWSGIでサーバーの状態（Stats）を見るには、[uWSGI Stats Server](https://uwsgi-docs.readthedocs.io/en/latest/StatsServer.html)を使えばよい
* `uwsgi`の起動オプションとして設定すれば、利用可能
* データはソケット（HTTP含む）で取得できる
* ただし、取得できる項目がどういった意味なのかは特にドキュメンテーションされていない様子…
    * ソースコードを見て把握するしかなさそう


# モチベーション
uWSGIを使ってアプリケーションをデプロイ、公開する際に、uWSGIからメトリクスを取得したい、というのが動機です。

[The uWSGI Stats Server](https://uwsgi-docs.readthedocs.io/en/latest/StatsServer.html)

で、ページを見つけたはいいものの、取得できる項目のサンプルはありますが、それがどういう意味家はドキュメントには見当たらず、日本語情報もなさそうです…。

といって、このページでそれを解説するわけでもないのですが。


# そもそも、uWSGIでメトリクスを取得するには？
前述のドキュメントに記載してある、Stats Serverを使う方法と

[The uWSGI Stats Server](https://uwsgi-docs.readthedocs.io/en/latest/StatsServer.html)

Metrics subsystemを使ってプラグインを書く方法があるようです。

[The Metrics subsystem](https://uwsgi-docs.readthedocs.io/en/latest/Metrics.html)

たとえば、DatadogはこのMetrics subsystemを使って、取得したメトリクスをDatadogに送信するプラグインを作成しています。

[uwsgi-dogstatsd](https://github.com/DataDog/uwsgi-dogstatsd)

ええ、C言語で書かれていますね…。

今回は、簡単にメトリクスを取得できるStats Serverを使用します。


# 環境
今回は、こちらの環境で確認します。

```python
$ python3 -V
Python 3.6.7


$ uwsgi --version
2.0.18
```

# Stats Serverを有効にする
今回のuWSGIのメトリクスを取得するにあたって、特に「処理中のリクエスト数（多重度）」を知りたいという要求があったので、以下のようにちょっとスリープを入れるWSGIスクリプトを用意しました。

app.py

```
import time

def application(env, start_response):
    print('waiting...')
    time.sleep(10)
    
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b'Hello WSGI!!']
```

こちらを使って、uWSGIを起動します。

```shell
$ uwsgi --http :8000 --stats :9000 --wsgi-file app.py
```

これで、通常のHTTPリクエストは`8000`ポートで受け付け、Statsは`9000`ポートで取得できるようになっています。

ドキュメントを見ていると、他にUnixドメインソケットも利用できそうな感じですね。

```shell
--stats 127.0.0.1:1717
--stats /tmp/statsock
--stats :5050
--stats @foobar
# Any of the above socket types can also return stats using HTTP
--stats 127.0.0.1:1717 --stats-http
```

確認してみましょう。先程の起動方法ではTCP待ち受けるようにStats Serverを起動しているので、`nc`コマンドで確認します。

```shell
$ nc localhost 9000
{
	"version":"2.0.18",
	"listen_queue":0,
	"listen_queue_errors":0,
	"signal_queue":0,
	"load":0,
	"pid":92625,
	"uid":1000,
	"gid":1000,
	"cwd":"/path",
	"locks":[
		{
			"user 0":0
		},
		{
			"signal":0
		},
		{
			"filemon":0
		},
		{
			"timer":0
		},
		{
			"rbtimer":0
		},
		{
			"cron":0
		},
		{
			"rpc":0
		},
		{
			"snmp":0
		}
	],
	"sockets":[
		{
			"name":"127.0.0.1:46175",
			"proto":"uwsgi",
			"queue":0,
			"max_queue":100,
			"shared":0,
			"can_offload":0
		}
	],
	"workers":[
		{
			"id":1,
			"pid":92626,
			"accepting":1,
			"requests":0,
			"delta_requests":0,
			"exceptions":0,
			"harakiri_count":0,
			"signals":0,
			"signal_queue":0,
			"status":"idle",
			"rss":0,
			"vsz":0,
			"running_time":0,
			"last_spawn":1557739876,
			"respawn_count":1,
			"tx":0,
			"avg_rt":0,
			"apps":[
				{
					"id":0,
					"modifier1":0,
					"mountpoint":"",
					"startup_time":0,
					"requests":0,
					"exceptions":0,
					"chdir":""
				}
			],
			"cores":[
				{
					"id":0,
					"requests":0,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":0,
					"vars":[

					],
					"req_info":					{

					}
				}
			]
		}
	]
}
```

こんな感じで情報が取得できました。

ちなみに、HTTPで取得したい場合は`--stats-http`を付与します。

```shell
$ uwsgi --http :8000 --stats :9000 --stats-http --wsgi-file app.py 
```

これで、`curl`などで取得できるようになります。

```shell
$ curl localhost:9000
{
	"version":"2.0.18",
	"listen_queue":0,
	"listen_queue_errors":0,
	"signal_queue":0,
	"load":0,
	"pid":92775,
	"uid":1000,
	"gid":1000,
	"cwd":"/home/ippei/projects/charon/IdeaProjects/uwsgi-stats",
	"locks":[
		{
			"user 0":0
		},
		{
			"signal":0
		},
		{
			"filemon":0
		},
		{
			"timer":0
		},
		{
			"rbtimer":0
		},
		{
			"cron":0
		},
		{
			"rpc":0
		},
		{
			"snmp":0
		}
	],
	"sockets":[
		{
			"name":"127.0.0.1:41235",
			"proto":"uwsgi",
			"queue":0,
			"max_queue":100,
			"shared":0,
			"can_offload":0
		}
	],
	"workers":[
		{
			"id":1,
			"pid":92776,
			"accepting":1,
			"requests":0,
			"delta_requests":0,
			"exceptions":0,
			"harakiri_count":0,
			"signals":0,
			"signal_queue":0,
			"status":"idle",
			"rss":0,
			"vsz":0,
			"running_time":0,
			"last_spawn":1557740070,
			"respawn_count":1,
			"tx":0,
			"avg_rt":0,
			"apps":[
				{
					"id":0,
					"modifier1":0,
					"mountpoint":"",
					"startup_time":0,
					"requests":0,
					"exceptions":0,
					"chdir":""
				}
			],
			"cores":[
				{
					"id":0,
					"requests":0,
					"static_requests":0,
					$ curl localhost:9000
{
	"version":"2.0.18",
	"listen_queue":0,
	"listen_queue_errors":0,
	"signal_queue":0,
	"load":0,
	"pid":92775,
	"uid":1000,
	"gid":1000,
	"cwd":"/path",
	"locks":[
		{
			"user 0":0
		},
		{
			"signal":0
		},
		{
			"filemon":0
		},
		{
			"timer":0
		},
		{
			"rbtimer":0
		},
		{
			"cron":0
		},
		{
			"rpc":0
		},
		{
			"snmp":0
		}
	],
	"sockets":[
		{
			"name":"127.0.0.1:41235",
			"proto":"uwsgi",
			"queue":0,
			"max_queue":100,
			"shared":0,
			"can_offload":0
		}
	],
	"workers":[
		{
			"id":1,
			"pid":92776,
			"accepting":1,
			"requests":0,
			"delta_requests":0,
			"exceptions":0,
			"harakiri_count":0,
			"signals":0,
			"signal_queue":0,
			"status":"idle",
			"rss":0,
			"vsz":0,
			"running_time":0,
			"last_spawn":1557740070,
			"respawn_count":1,
			"tx":0,
			"avg_rt":0,
			"apps":[
				{
					"id":0,
					"modifier1":0,
					"mountpoint":"",
					"startup_time":0,
					"requests":0,
					"exceptions":0,
					"chdir":""
				}
			],
			"cores":[
				{
					"id":0,
					"requests":0,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":0,
					"vars":[

					],
					"req_info":					{

					}
				}
			]
		}
	]
}"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":0,
					"vars":[

					],
					"req_info":					{

					}
				}
			]
		}
	]
}
```

今回は、このまま`--stats-http`を付けたままにしましょう。

ちょっと簡易的に、`curl`をバックグラウンドで動かして5回連続してリクエストを投げてみます。

```shell
$ curl localhost:8000 &
$ curl localhost:8000 &
$ curl localhost:8000 &
$ curl localhost:8000 &
$ curl localhost:8000 &
```

この待っている間に、Statsを取得してみます。

```shell
$ curl localhost:9000
{
	"version":"2.0.18",
	"listen_queue":4,
	"listen_queue_errors":0,
	"signal_queue":0,
	"load":4,
	"pid":92775,
	"uid":1000,
	"gid":1000,
	"cwd":"/path",
	"locks":[
		{
			"user 0":0
		},
		{
			"signal":0
		},
		{
			"filemon":0
		},
		{
			"timer":0
		},
		{
			"rbtimer":0
		},
		{
			"cron":0
		},
		{
			"rpc":0
		},
		{
			"snmp":0
		}
	],
	"sockets":[
		{
			"name":"127.0.0.1:41235",
			"proto":"uwsgi",
			"queue":4,
			"max_queue":100,
			"shared":0,
			"can_offload":0
		}
	],
	"workers":[
		{
			"id":1,
			"pid":92776,
			"accepting":1,
			"requests":9,
			"delta_requests":9,
			"exceptions":0,
			"harakiri_count":0,
			"signals":0,
			"signal_queue":0,
			"status":"busy",
			"rss":0,
			"vsz":0,
			"running_time":90069190,
			"last_spawn":1557740070,
			"respawn_count":1,
			"tx":513,
			"avg_rt":9986221,
			"apps":[
				{
					"id":0,
					"modifier1":0,
					"mountpoint":"",
					"startup_time":0,
					"requests":10,
					"exceptions":0,
					"chdir":""
				}
			],
			"cores":[
				{
					"id":0,
					"requests":9,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":1,
					"vars":[
"REQUEST_METHOD=GET",
"REQUEST_URI=/",
"PATH_INFO=/",
"QUERY_STRING=",
"SERVER_PROTOCOL=HTTP/1.1",
"SCRIPT_NAME=",
"SERVER_NAME=myserver",
"SERVER_PORT=8000",
"UWSGI_ROUTER=http",
"REMOTE_ADDR=127.0.0.1",
"REMOTE_PORT=60051",
"HTTP_HOST=localhost:8000",
"HTTP_USER_AGENT=curl/7.58.0",
"HTTP_ACCEPT=*/*",
""
					],
					"req_info":					{
						"request_start":1557740273
					}
				}
			]
		}
	]
}
```

`listen_queue`が4で

```
	"listen_queue":4,
```

`in_request`が1なので

```
					"in_request":1,
```

現在リクエストを処理していて、待っているのが4つということがわかります。

ちなみに、この`in_request`の1は、数ではなく意味的には`boolean`っぽいです…。

https://github.com/unbit/uwsgi/blob/2.0.18/plugins/gevent/gevent.c#L226

今回は、スレッド数もなにもチューニングしていないですから、待ちが多いですね…。

ここで、プロセス数を2、スレッド数を2にしてみましょう。

```shell
$ uwsgi --http :8000 --stats :9000 --stats-http --master --processes 2 --threads 2 --wsgi-file app.py 
```

同じように、リクエストを投げてみます。

```shell
$ curl localhost:8000 &
$ curl localhost:8000 &
$ curl localhost:8000 &
$ curl localhost:8000 &
$ curl localhost:8000 &
```

Statsを取得してみましょう。`workers`と配下の`cores`が増え、4つまで同時にリクエストを処理できるようになっています。

```shell
$ curl localhost:9000
{
	"version":"2.0.18",
	"listen_queue":1,
	"listen_queue_errors":0,
	"signal_queue":0,
	"load":1,
	"pid":93145,
	"uid":1000,
	"gid":1000,
	"cwd":"/path",
	"locks":[
		{
			"user 0":0
		},
		{
			"signal":0
		},
		{
			"filemon":0
		},
		{
			"timer":0
		},
		{
			"rbtimer":0
		},
		{
			"cron":0
		},
		{
			"rpc":0
		},
		{
			"snmp":0
		}
	],
	"sockets":[
		{
			"name":"127.0.0.1:43177",
			"proto":"uwsgi",
			"queue":1,
			"max_queue":100,
			"shared":0,
			"can_offload":0
		}
	],
	"workers":[
		{
			"id":1,
			"pid":93146,
			"accepting":1,
			"requests":0,
			"delta_requests":0,
			"exceptions":0,
			"harakiri_count":0,
			"signals":0,
			"signal_queue":0,
			"status":"busy",
			"rss":0,
			"vsz":0,
			"running_time":0,
			"last_spawn":1557740578,
			"respawn_count":1,
			"tx":0,
			"avg_rt":0,
			"apps":[
				{
					"id":0,
					"modifier1":0,
					"mountpoint":"",
					"startup_time":0,
					"requests":2,
					"exceptions":0,
					"chdir":""
				}
			],
			"cores":[
				{
					"id":0,
					"requests":0,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":1,
					"vars":[
"REQUEST_METHOD=GET",
"REQUEST_URI=/",
"PATH_INFO=/",
"QUERY_STRING=",
"SERVER_PROTOCOL=HTTP/1.1",
"SCRIPT_NAME=",
"SERVER_NAME=myserver",
"SERVER_PORT=8000",
"UWSGI_ROUTER=http",
"REMOTE_ADDR=127.0.0.1",
"REMOTE_PORT=55956",
"HTTP_HOST=localhost:8000",
"HTTP_USER_AGENT=curl/7.58.0",
"HTTP_ACCEPT=*/*",
""
					],
					"req_info":					{
						"request_start":1557740581
					}
				},
				{
					"id":1,
					"requests":0,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":1,
					"vars":[
"REQUEST_METHOD=GET",
"REQUEST_URI=/",
"PATH_INFO=/",
"QUERY_STRING=",
"SERVER_PROTOCOL=HTTP/1.1",
"SCRIPT_NAME=",
"SERVER_NAME=myserver",
"SERVER_PORT=8000",
"UWSGI_ROUTER=http",
"REMOTE_ADDR=127.0.0.1",
"REMOTE_PORT=56980",
"HTTP_HOST=localhost:8000",
"HTTP_USER_AGENT=curl/7.58.0",
"HTTP_ACCEPT=*/*",
""
					],
					"req_info":					{
						"request_start":1557740582
					}
				}
			]
		},
		{
			"id":2,
			"pid":93147,
			"accepting":1,
			"requests":0,
			"delta_requests":0,
			"exceptions":0,
			"harakiri_count":0,
			"signals":0,
			"signal_queue":0,
			"status":"busy",
			"rss":0,
			"vsz":0,
			"running_time":0,
			"last_spawn":1557740578,
			"respawn_count":1,
			"tx":0,
			"avg_rt":0,
			"apps":[
				{
					"id":0,
					"modifier1":0,
					"mountpoint":"",
					"startup_time":0,
					"requests":2,
					"exceptions":0,
					"chdir":""
				}
			],
			"cores":[
				{
					"id":0,
					"requests":0,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":1,
					"vars":[
"REQUEST_METHOD=GET",
"REQUEST_URI=/",
"PATH_INFO=/",
"QUERY_STRING=",
"SERVER_PROTOCOL=HTTP/1.1",
"SCRIPT_NAME=",
"SERVER_NAME=myserver",
"SERVER_PORT=8000",
"UWSGI_ROUTER=http",
"REMOTE_ADDR=127.0.0.1",
"REMOTE_PORT=54932",
"HTTP_HOST=localhost:8000",
"HTTP_USER_AGENT=curl/7.58.0",
"HTTP_ACCEPT=*/*",
""
					],
					"req_info":					{
						"request_start":1557740581
					}
				},
				{
					"id":1,
					"requests":0,
					"static_requests":0,
					"routed_requests":0,
					"offloaded_requests":0,
					"write_errors":0,
					"read_errors":0,
					"in_request":1,
					"vars":[
"REQUEST_METHOD=GET",
"REQUEST_URI=/",
"PATH_INFO=/",
"QUERY_STRING=",
"SERVER_PROTOCOL=HTTP/1.1",
"SCRIPT_NAME=",
"SERVER_NAME=myserver",
"SERVER_PORT=8000",
"UWSGI_ROUTER=http",
"REMOTE_ADDR=127.0.0.1",
"REMOTE_PORT=58004",
"HTTP_HOST=localhost:8000",
"HTTP_USER_AGENT=curl/7.58.0",
"HTTP_ACCEPT=*/*",
""
					],
					"req_info":					{
						"request_start":1557740582
					}
				}
			]
		}
	]
}
```

今度は、キューが1になります。

```
	"listen_queue":1,
```

こんな感じで挙動を見つつ、ソースコードを見つつ、Statsを把握していくのだと思います。

大変。