Files

606 lines
13 KiB
Markdown

---
name: ngx-http-monitoring-client
description: Use this skill when connecting clients or agents to an ngx_http_monitoring_module service, querying its JSON/SSE/Prometheus endpoints, reading API response structures, debugging access problems, or integrating dashboards/health checks. Supports deployments protected by Nginx Basic Auth and/or monitor_api_token.
---
# ngx_http_monitoring_module Client
Use this skill to consume a deployed `ngx_http_monitoring_module` service from scripts, agents, dashboards, health checks, CI, or observability clients.
## Inputs To Determine
Before calling the service, identify:
- `base_url`: root URL that serves `/monitor`, for example `http://127.0.0.1:8080`
- optional API token: sent as `X-Monitor-Token`, `Authorization: Bearer`, or `?token=...`
- optional Nginx Basic Auth credentials: `username:password`
- target endpoint: dashboard, JSON API, SSE, Prometheus, or health
Never invent credentials. Ask the user, read documented deployment config, or use provided environment variables.
Recommended environment variables:
```sh
MONITOR_BASE_URL=http://127.0.0.1:8080
MONITOR_TOKEN=change-me
MONITOR_BASIC_AUTH=user:password
```
## Endpoints
- `GET /monitor` - embedded HTML dashboard
- `GET /monitor/api` - full JSON metrics
- `GET /monitor/api/system` - CPU, load, memory, swap, uptime
- `GET /monitor/api/nginx` - Nginx connection/request/worker metrics
- `GET /monitor/api/network` - network interfaces and counters
- `GET /monitor/api/disk` - disk and filesystem metrics
- `GET /monitor/api/processes` - process, TCP, socket, and worker stats
- `GET /monitor/api/upstreams` - observed upstream/backend stats
- `GET /monitor/api/connections` - connection/SSE/keepalive counters
- `GET /monitor/api/requests` - request counters, histograms, percentiles, top URLs
- `GET /monitor/live` - Server-Sent Events stream
- `GET /monitor/metrics` - Prometheus text format
- `GET /monitor/health` - lightweight JSON health check
## API Structures
All JSON API responses are compact, versioned, and timestamped. Treat unknown fields as forward-compatible additions, and treat missing sections as disabled collectors, unsupported Nginx build options, or unavailable Linux `/proc` data.
Common envelope:
```json
{
"version": 1,
"module": "1.0.0",
"timestamp": 1710000000,
"msec": 123,
"scope": "full",
"pid": 12345
}
```
Endpoint-specific API routes such as `/monitor/api/system` return the common envelope plus one top-level field matching the route. The examples below show that section's value unless explicitly shown as a complete response.
`GET /monitor/api` returns the envelope plus:
```json
{
"system": {},
"nginx": {},
"network": {},
"disk": {},
"processes": {},
"upstreams": [],
"connections": {},
"requests": {},
"history": []
}
```
`GET /monitor/api/system`:
```json
{
"cpu": {
"usage": 12.5,
"cores": 8,
"load": [0.12, 0.20, 0.30]
},
"memory": {
"total": 16777216,
"available": 8388608,
"free": 4194304,
"used": 8388608,
"used_pct": 50.0
},
"swap": {
"total": 2097152,
"free": 1048576,
"used_pct": 50.0
},
"uptime": 123456
}
```
`GET /monitor/api/nginx`:
```json
{
"connections": {
"accepted": 1000,
"handled": 1000,
"active": 20,
"reading": 1,
"writing": 4,
"waiting": 15
},
"requests": {
"total": 50000,
"responses": 49990,
"requests_per_sec": 125.5,
"responses_per_sec": 125.3
},
"ssl": {
"requests": 100,
"handshakes": 20
},
"keepalive": {
"requests": 250
},
"workers": []
}
```
`GET /monitor/api/network`:
```json
{
"rx_bytes": 123456789,
"tx_bytes": 987654321,
"rx_packets": 10000,
"tx_packets": 12000,
"rx_errors": 0,
"tx_errors": 0,
"interfaces": [
{
"name": "eth0",
"rx_bytes": 123456789,
"tx_bytes": 987654321,
"rx_packets": 10000,
"tx_packets": 12000,
"rx_errors": 0,
"tx_errors": 0,
"up": true
}
]
}
```
`GET /monitor/api/disk`:
```json
{
"reads": 1000,
"writes": 2000,
"read_bytes": 4096000,
"write_bytes": 8192000,
"devices": [
{
"name": "sda",
"reads": 1000,
"writes": 2000,
"read_bytes": 4096000,
"write_bytes": 8192000,
"io_ms": 120
}
],
"filesystems": [
{
"path": "/",
"type": "ext4",
"total": 107374182400,
"used": 53687091200,
"free": 53687091200,
"avail": 53687091200,
"files": 1000000,
"files_free": 750000
}
]
}
```
`GET /monitor/api/processes`:
```json
{
"process_count": 140,
"tcp": {
"established": 20,
"listen": 8
},
"sockets": {
"used": 512,
"tcp": 120,
"udp": 16
},
"workers": []
}
```
`GET /monitor/api/connections`:
```json
{
"accepted": 1000,
"handled": 1000,
"active": 20,
"reading": 1,
"writing": 4,
"waiting": 15,
"ssl_requests": 100,
"ssl_handshakes": 20,
"keepalive_requests": 250,
"sse_clients": 2,
"sse_events": 500,
"rate_limited": 0
}
```
`GET /monitor/api/requests`:
```json
{
"total": 50000,
"responses": 49990,
"bytes": 104857600,
"requests_per_sec": 125.5,
"responses_per_sec": 125.3,
"error_rate": 0.0078,
"latency": {
"avg": 8.4,
"p50": 4,
"p90": 15,
"p95": 22,
"p99": 80
},
"status": {
"1xx": 0,
"2xx": 49000,
"3xx": 600,
"4xx": 300,
"5xx": 90
},
"methods": {
"GET": 45000,
"POST": 4000,
"PUT": 100,
"DELETE": 50,
"HEAD": 500,
"OPTIONS": 200,
"PATCH": 50,
"OTHER": 100
},
"latency_histogram": [
{
"le": 1,
"count": 100
},
{
"le": "+Inf",
"count": 50000
}
],
"size_histogram": [
{
"le": 512,
"count": 10000
},
{
"le": "+Inf",
"count": 50000
}
],
"top_urls": [],
"user_agents": []
}
```
`GET /monitor/api/upstreams`:
```json
[
{
"peer": "127.0.0.1:9000",
"requests": 1000,
"failures": 2,
"status_4xx": 10,
"status_5xx": 2,
"avg_latency": 12.5,
"last_seen": 1710000000
}
]
```
History samples in `GET /monitor/api`:
```json
[
{
"timestamp": 1710000000,
"cpu": 12.5,
"memory": 50.0,
"swap": 0.0,
"rps": 125.5,
"responses_per_sec": 125.3,
"network_rx_per_sec": 1024,
"network_tx_per_sec": 2048,
"disk_read_per_sec": 0,
"disk_write_per_sec": 4096,
"latency_p95": 22,
"requests_total": 50000,
"status_4xx": 300,
"status_5xx": 90
}
]
```
Shared nested structures:
```json
{
"worker": {
"slot": 0,
"pid": 12345,
"active": true,
"requests": 10000,
"bytes": 10485760,
"errors": 3,
"vm_size": 102400,
"vm_rss": 20480,
"voluntary_ctxt": 100,
"nonvoluntary_ctxt": 5,
"last_seen": 1710000000
},
"top_url": {
"url": "/api/orders",
"hits": 1000,
"errors": 3,
"bytes": 1048576,
"avg_latency": 7.8,
"last_seen": 1710000000
},
"user_agent": {
"user_agent": "curl/8.0.1",
"hits": 100,
"errors": 0,
"bytes": 20480,
"avg_latency": 2.1,
"last_seen": 1710000000
}
}
```
SSE `GET /monitor/live` sends `metrics` events whose `data` payload has this structure:
```json
{
"version": 1,
"timestamp": 1710000000,
"sequence": 42,
"system": {
"cpu": {
"usage": 12.5,
"cores": 8
},
"memory": {
"used_pct": 50.0,
"total": 16777216,
"available": 8388608
}
},
"requests": {
"total": 50000,
"requests_per_sec": 125.5,
"responses_per_sec": 125.3,
"latency": {
"p95": 22,
"p99": 80
},
"status": {
"4xx": 300,
"5xx": 90
}
},
"connections": {
"active": 20,
"reading": 1,
"writing": 4,
"waiting": 15,
"sse_clients": 2,
"keepalive_requests": 250
},
"network": {
"rx_bytes": 123456789,
"tx_bytes": 987654321
},
"disk": {
"read_bytes": 4096000,
"write_bytes": 8192000
}
}
```
`GET /monitor/metrics` exposes Prometheus text metrics including:
```text
nginx_monitor_requests_total
nginx_monitor_active_connections
nginx_monitor_cpu_usage_ratio
nginx_monitor_memory_used_ratio
nginx_monitor_latency_p95_ms
```
`GET /monitor/health` returns a complete health response:
```json
{
"version": 1,
"module": "1.0.0",
"timestamp": 1710000000,
"msec": 123,
"scope": "health",
"pid": 12345,
"status": "ok",
"generation": 100,
"sse_clients": 2
}
```
## Auth Rules
If the service is behind Nginx Basic Auth, include Basic Auth on every request, including `/monitor`, `/monitor/live`, and `/monitor/metrics`.
If `monitor_api_token` is configured, include the token on non-dashboard endpoints. Prefer a header over query string unless the client cannot set headers.
Preferred headers:
```sh
-H "X-Monitor-Token: $MONITOR_TOKEN"
```
Alternative bearer header:
```sh
-H "Authorization: Bearer $MONITOR_TOKEN"
```
Basic Auth with curl:
```sh
-u "$MONITOR_BASIC_AUTH"
```
When both Basic Auth and API token are enabled, send both.
Do not print secrets in final answers, logs, or copied command output. Redact with `<redacted>` when summarizing.
## Curl Patterns
Build arguments conditionally:
```sh
base="${MONITOR_BASE_URL:-http://127.0.0.1:8080}"
auth_args=""
token_args=""
if [ "${MONITOR_BASIC_AUTH:-}" != "" ]; then
auth_args="-u $MONITOR_BASIC_AUTH"
fi
if [ "${MONITOR_TOKEN:-}" != "" ]; then
token_args="-H X-Monitor-Token: $MONITOR_TOKEN"
fi
```
For exact shell execution, prefer explicit commands instead of string-expanded credentials when possible:
```sh
curl -fsS -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
"$MONITOR_BASE_URL/monitor/api"
```
Without token:
```sh
curl -fsS -u "$MONITOR_BASIC_AUTH" "$MONITOR_BASE_URL/monitor/api"
```
Without Basic Auth:
```sh
curl -fsS -H "X-Monitor-Token: $MONITOR_TOKEN" "$MONITOR_BASE_URL/monitor/api"
```
Health check:
```sh
curl -fsS -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
"$MONITOR_BASE_URL/monitor/health"
```
Prometheus:
```sh
curl -fsS -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
"$MONITOR_BASE_URL/monitor/metrics"
```
SSE sample:
```sh
curl -fsS -N -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
"$MONITOR_BASE_URL/monitor/live"
```
## Browser And Dashboard
Open:
```text
<base_url>/monitor
```
If the API token is required and the browser cannot set headers for `EventSource`, append the token:
```text
<base_url>/monitor?token=<token>
```
The dashboard forwards the `token` query parameter to API and SSE calls. Basic Auth is handled by the browser after the Nginx auth challenge.
## JavaScript Client
Use headers for JSON polling:
```js
const baseUrl = process.env.MONITOR_BASE_URL ?? "http://127.0.0.1:8080";
const token = process.env.MONITOR_TOKEN;
const headers = token ? { "X-Monitor-Token": token } : {};
const res = await fetch(`${baseUrl}/monitor/api`, { headers });
if (!res.ok) throw new Error(`monitor api failed: ${res.status}`);
const metrics = await res.json();
```
For browser `EventSource`, headers are not available. Use query token if needed:
```js
const url = new URL("/monitor/live", baseUrl);
if (token) url.searchParams.set("token", token);
const events = new EventSource(url);
events.addEventListener("metrics", (event) => {
const metrics = JSON.parse(event.data);
});
```
If Basic Auth is required in browser clients, rely on the browser's authenticated session. Do not embed `user:password` in URLs.
## Interpreting Responses
Every JSON response includes:
- `version`
- `module`
- `timestamp`
- `scope`
Common fields:
- CPU usage: `system.cpu.usage`
- memory used percent: `system.memory.used_pct`
- request rate: `requests.requests_per_sec` or `nginx.requests.requests_per_sec`
- latency: `requests.latency.p50`, `p90`, `p95`, `p99`
- errors: `requests.status["4xx"]`, `requests.status["5xx"]`
- active connections: `connections.active` or `nginx.connections.active`
Treat missing fields as disabled collectors, unsupported Nginx build options, or insufficient traffic. For exact active/reading/writing/waiting connection counters, the Nginx build must include `--with-http_stub_status_module`.
## Troubleshooting
- `401`: Basic Auth or API token is missing or wrong. If both are configured, send both.
- `403`: `monitor_allow` / `monitor_deny` blocked the client IP.
- `404`: `monitor on`, `monitor_api on`, or `monitor_sse on` may not be enabled in the matching location.
- `429`: `monitor_rate_limit` was exceeded.
- Empty connection counters: Nginx may not have been built with stub status support.
- SSE connects then drops: proxy buffering or idle timeout may be in front of the service. Set `proxy_buffering off` and allow long-lived responses.
## Security Defaults
When integrating with another client:
- prefer HTTPS
- prefer header token over query token
- avoid logging headers, URLs containing `token=`, or Basic Auth strings
- use least-privilege network ACLs in front of `/monitor`
- avoid embedding Basic Auth credentials in browser source