Skip to content

Commit c3b101a

Browse files
committed
Add PHP Status API implementation plan
- Document technical design for /status/applications/<name>/php endpoint - Define data structures (nxt_php_status_t, extended nxt_status_app_t) - Describe IPC mechanism for stats collection from workers - Plan opcache integration via ZCSG macros - Outline 7-phase implementation plan with tests - Reference nginx/unit PRs nginx#1089, nginx#1378, commit 707f4ef Part of P2 from roadmap/unit-php.md assisted-by: Qwen Code Signed-off-by: Andy Postnikov <apostnikov@gmail.com>
1 parent 2698c83 commit c3b101a

1 file changed

Lines changed: 374 additions & 0 deletions

File tree

php_status_implementation.md

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
# Implement PHP Status API (P2 from roadmap)
2+
3+
## Summary
4+
Implement `/status/applications/<name>/php` endpoint to expose PHP-specific statistics including opcache metrics, JIT state, request counters, GC stats, and memory usage.
5+
6+
## Roadmap Reference
7+
This implements **P2** from the [PHP roadmap](roadmap/unit-php.md):
8+
9+
> **P2. Status API for PHP.**
10+
> - `/status/applications/<name>/php` returns: opcache stats (hits, misses, cached scripts, memory used/free, interned strings), JIT state, request counters (total, active, rejected), last GC run, per-worker memory high-water-mark.
11+
> - Implementation: one SAPI internal call per worker that scrapes `opcache_get_status()` equivalents from C (`accel_shared_globals`, `ZCSG` macros) without needing a PHP function call.
12+
> - **Wins:** removes the "is opcache actually hot" mystery; feeds Prometheus.
13+
> - **Effort:** ~1 week.
14+
15+
## Motivation
16+
17+
### Current State
18+
Unit's `/status` endpoint provides generic statistics:
19+
- Connection counts (accepted, active, idle, closed)
20+
- Request totals
21+
- Application process counts (running, starting, idle)
22+
23+
But **no PHP-specific metrics** are exposed, making it impossible to:
24+
- Monitor opcache effectiveness (hit rate, memory usage)
25+
- Debug performance issues (JIT status, GC frequency)
26+
- Optimize PHP configuration (memory limits, preload effectiveness)
27+
- Feed monitoring systems (Prometheus, Grafana)
28+
29+
### Comparison
30+
- **PHP-FPM**: Has `pm.status_path` with detailed process manager stats
31+
- **FrankenPHP**: Exposes opcache and request metrics via `/metrics` endpoint
32+
- **FreeUnit**: Currently has no PHP-specific observability
33+
34+
## Proposed Response Format
35+
36+
```json
37+
{
38+
"opcache": {
39+
"hits": 12345,
40+
"misses": 234,
41+
"cached_scripts": 89,
42+
"memory_used": 4194304,
43+
"memory_free": 131072,
44+
"interned_strings_used": 262144,
45+
"interned_strings_free": 32768
46+
},
47+
"jit": {
48+
"buffer_size": 134217728,
49+
"memory_used": 1048576,
50+
"enabled": true
51+
},
52+
"requests": {
53+
"total": 5000,
54+
"active": 2,
55+
"rejected": 0
56+
},
57+
"gc": {
58+
"runs": 15,
59+
"last_run_time": 1234567890
60+
},
61+
"memory": {
62+
"peak": 8388608,
63+
"current": 2097152
64+
}
65+
}
66+
```
67+
68+
## Technical Design
69+
70+
### Architecture
71+
72+
```
73+
┌─────────────┐ IPC ┌──────────────┐
74+
│ Router │─────────────>│ PHP Worker 1 │
75+
│ (collects │ │ (collects │
76+
│ & aggrees)│<─────────────│ ZCSG stats) │
77+
└─────────────┘ IPC └──────────────┘
78+
79+
│ IPC ┌──────────────┐
80+
└───────────────────────────────────>│ PHP Worker N │
81+
│ (collects │
82+
│ ZCSG stats) │
83+
└──────────────┘
84+
```
85+
86+
### Key Components
87+
88+
#### 1. Data Structures (`src/nxt_status.h`)
89+
90+
Extend `nxt_status_app_t` to include PHP-specific stats:
91+
92+
```c
93+
typedef struct {
94+
nxt_str_t name;
95+
uint32_t active_requests;
96+
uint32_t pending_processes;
97+
uint32_t processes;
98+
uint32_t idle_processes;
99+
100+
/* PHP-specific - aggregated from all workers */
101+
nxt_php_status_t *php_stats;
102+
uint32_t php_workers_count;
103+
} nxt_status_app_t;
104+
```
105+
106+
New structure for PHP stats (`src/nxt_php_sapi.c` or new `src/nxt_php_status.h`):
107+
108+
```c
109+
typedef struct {
110+
/* Opcache stats from accel_shared_globals */
111+
uint64_t opcache_hits;
112+
uint64_t opcache_misses;
113+
uint64_t opcache_cached_scripts;
114+
uint64_t opcache_memory_used;
115+
uint64_t opcache_memory_free;
116+
uint64_t opcache_interned_strings_used;
117+
uint64_t opcache_interned_strings_free;
118+
119+
/* JIT stats */
120+
uint64_t jit_buffer_size;
121+
uint64_t jit_memory_used;
122+
bool jit_enabled;
123+
124+
/* Request counters from SAPI globals */
125+
uint64_t requests_total;
126+
uint64_t requests_active;
127+
uint64_t requests_rejected;
128+
129+
/* GC stats from Zend GC globals */
130+
uint64_t gc_runs;
131+
uint64_t gc_last_run_time;
132+
133+
/* Memory from Zend allocator */
134+
uint64_t memory_peak;
135+
uint64_t memory_current;
136+
} nxt_php_status_t;
137+
```
138+
139+
#### 2. IPC Mechanism
140+
141+
Reuse existing status request pattern from `nxt_router_status_handler()`:
142+
143+
1. Router sends `NXT_PORT_MSG_STATUS` to each PHP worker
144+
2. Worker collects stats via `nxt_php_collect_status()`
145+
3. Worker responds with serialized JSON via `NXT_PORT_MSG_RPC_READY`
146+
4. Router aggregates responses from all workers
147+
148+
#### 3. Opcache Integration
149+
150+
Access opcache globals directly from C (no PHP function call needed):
151+
152+
```c
153+
#include "ZendAccelerator.h"
154+
155+
/* Example: get opcache hit count */
156+
static uint64_t
157+
nxt_php_opcache_get_hits(void)
158+
{
159+
#if defined(HAVE_ZEND_ACCELERATOR_H)
160+
return ZCSG(stat).hits;
161+
#else
162+
return 0;
163+
#endif
164+
}
165+
```
166+
167+
**Build requirement**: Need to detect opcache headers at compile time.
168+
169+
#### 4. Version Compatibility
170+
171+
Use PHP version guards for API differences:
172+
173+
```c
174+
#if PHP_VERSION_ID >= 80000
175+
/* PHP 8+ API */
176+
#else
177+
/* PHP 7.x fallback */
178+
#endif
179+
180+
#if defined(ZTS)
181+
/* Thread-safe access with locks */
182+
#else
183+
/* Direct access */
184+
#endif
185+
```
186+
187+
## Implementation Plan
188+
189+
### Phase 1: Infrastructure (2-3 days)
190+
- [ ] Create `nxt_php_status_t` structure
191+
- [ ] Extend `nxt_status_app_t` with PHP stats pointer
192+
- [ ] Add basic request counter collection from `SG(requests)`
193+
- [ ] Add memory stats from `zend_memory_usage()`
194+
195+
### Phase 2: Opcache Integration (2-3 days)
196+
- [ ] Add opcache header detection in `auto/modules/php`
197+
- [ ] Implement `nxt_php_collect_opcache_status()`
198+
- [ ] Handle missing opcache gracefully (return null/zero values)
199+
- [ ] Test with opcache enabled/disabled
200+
201+
### Phase 3: JIT and GC Stats (1-2 days)
202+
- [ ] Add JIT buffer size detection
203+
- [ ] Add GC stats from `GC_G(gc_runs)`
204+
- [ ] Test across PHP 8.1-8.5
205+
206+
### Phase 4: IPC & Aggregation (2-3 days)
207+
- [ ] Implement status request from router to workers
208+
- [ ] Implement worker response handler
209+
- [ ] Aggregate stats from multiple workers (sum/average as appropriate)
210+
- [ ] Handle timeouts and worker failures
211+
212+
### Phase 5: JSON Serialization (1 day)
213+
- [ ] Add `nxt_php_status_to_json()` function
214+
- [ ] Integrate with `nxt_status_get()` in `src/nxt_status.c`
215+
- [ ] Ensure proper memory pool allocation
216+
217+
### Phase 6: Tests (2 days)
218+
- [ ] Create `test/test_php_status.py` with pytest tests
219+
- [ ] Create test fixtures (`test/php/status/`)
220+
- [ ] Test single and multiple worker scenarios
221+
- [ ] Test opcache warmup detection
222+
- [ ] Test security (DELETE/PUT should fail)
223+
224+
### Phase 7: Documentation (1 day)
225+
- [ ] Update `README.md` with PHP status section
226+
- [ ] Update `docs/unit-openapi.yaml` with new endpoint
227+
- [ ] Add example responses to documentation
228+
229+
## Test Coverage
230+
231+
### Test File: `test/test_php_status.py`
232+
233+
```python
234+
def test_php_status_endpoint_exists():
235+
"""Verify /status/applications/<name>/php returns data"""
236+
237+
def test_php_status_opcache_stats():
238+
"""Verify opcache metrics are present and accurate"""
239+
240+
def test_php_status_jit_stats():
241+
"""Verify JIT section exists (even if disabled)"""
242+
243+
def test_php_status_request_counters():
244+
"""Verify request counters increment correctly"""
245+
246+
def test_php_status_memory_stats():
247+
"""Verify memory peak/current are reported"""
248+
249+
def test_php_status_gc_stats():
250+
"""Verify GC runs counter is present"""
251+
252+
def test_php_status_multiple_workers():
253+
"""Verify stats aggregation across multiple workers"""
254+
255+
def test_php_status_opcache_warmup():
256+
"""Verify cache hits increase after warmup"""
257+
258+
def test_php_status_security():
259+
"""Verify DELETE/PUT methods are rejected"""
260+
```
261+
262+
### Test Fixtures
263+
264+
**test/php/status/index.php** - Basic status test script
265+
**test/php/status/opcache.php** - Script to exercise opcache
266+
267+
## Build Configuration
268+
269+
### Update `auto/modules/php`
270+
271+
Add feature probe for opcache:
272+
273+
```bash
274+
$echo_n "checking for opcache headers... "
275+
if try_cpp '
276+
#include "ZendAccelerator.h"
277+
int main(void) {
278+
zend_accel_shared_globals *g = &accel_shared_globals;
279+
return g->stat.hits;
280+
}'
281+
then
282+
NXT_PHP_HAVE_ACCELERATOR=YES
283+
NXT_PHP_CFLAGS="$NXT_PHP_CFLAGS -DHAVE_ZEND_ACCELERATOR_H"
284+
else
285+
NXT_PHP_HAVE_ACCELERATOR=NO
286+
fi
287+
```
288+
289+
Add optional flag:
290+
291+
```bash
292+
--with-php-status) NXT_PHP_STATUS=yes ;;
293+
--without-php-status) NXT_PHP_STATUS=no ;;
294+
```
295+
296+
## Risks & Mitigations
297+
298+
### Risk 1: Opcache headers unavailable
299+
**Problem**: Some PHP builds don't expose `ZendAccelerator.h`
300+
301+
**Mitigation**:
302+
- Use `#ifdef HAVE_ZEND_ACCELERATOR_H` guards
303+
- Return null/zero for opcache fields when unavailable
304+
- Document in user guide
305+
306+
### Risk 2: Performance impact
307+
**Problem**: Frequent status queries could impact performance
308+
309+
**Mitigation**:
310+
- Cache stats for 1-2 seconds
311+
- Make collection lightweight (no string operations in hot path)
312+
- Benchmark with ab/wrk
313+
314+
### Risk 3: ZTS thread safety
315+
**Problem**: Accessing globals under ZTS requires locks
316+
317+
**Mitigation**:
318+
- Use `TSRMLS_FETCH()` or `ts_resource()` for ZTS builds
319+
- Test extensively with ZTS-enabled PHP
320+
321+
### Risk 4: Version fragmentation
322+
**Problem**: Different PHP versions have different struct layouts
323+
324+
**Mitigation**:
325+
- Extensive use of `PHP_VERSION_ID` guards
326+
- Test matrix: PHP 8.1, 8.2, 8.3, 8.4, 8.5
327+
- Document minimum supported version (8.1+)
328+
329+
## Success Metrics
330+
331+
- [ ] Endpoint returns valid JSON with all expected fields
332+
- [ ] All tests pass on PHP 8.1-8.5 (NTS and ZTS)
333+
- [ ] Opcache stats match `opcache_get_status()` from PHP
334+
- [ ] No memory leaks under frequent querying (verified with valgrind)
335+
- [ ] Performance impact < 5% (measured with wrk)
336+
- [ ] Works with opcache enabled and disabled
337+
- [ ] Works with JIT enabled and disabled
338+
339+
## Related Issues & PRs
340+
341+
### nginx/unit references
342+
- PR #1089: Added `unit` section to `/status` endpoint (merged)
343+
- PR #1378: Updated OpenAPI spec for `/status/modules` (merged)
344+
- Commit 707f4ef8: Show list of loaded language modules
345+
346+
### Internal references
347+
- `roadmap/unit-php.md` - P2 Status API for PHP
348+
- `src/nxt_status.c` - Existing status collection logic
349+
- `src/nxt_router.c:nxt_router_status_handler()` - Status IPC pattern
350+
- `test/test_status.py` - Existing status tests (Python)
351+
352+
## Future Enhancements
353+
354+
### P3: Preload/Warm-up Hook
355+
Status API can verify preload effectiveness by comparing cached_scripts before/after warmup.
356+
357+
### P9: JIT-aware Defaults
358+
Use JIT stats to auto-tune `opcache.jit_buffer_size` based on actual usage.
359+
360+
### Prometheus Exporter
361+
Native `/metrics` endpoint in Prometheus format (separate future issue).
362+
363+
## References
364+
365+
- [PHP-FPM status documentation](https://www.php.net/manual/en/fpm.status.php)
366+
- [FrankenPHP metrics endpoint](https://frankenphp.dev/docs/metrics/)
367+
- [Opcache internals](https://github.com/php/php-src/blob/master/ext/opcache/ZendAccelerator.h)
368+
- [Zend GC globals](https://github.com/php/php-src/blob/master/Zend/zend_gc.h)
369+
370+
---
371+
372+
**Labels**: `enhancement`, `php`, `observability`, `status-api`, `opcache`
373+
**Milestone**: Near-term (1-3 months)
374+
**Assignee**: @andypost

0 commit comments

Comments
 (0)