Skip to content

Commit 0a294ac

Browse files
authored
Merge branch 'main' into fix/script_name
2 parents 91a6968 + efbbfa3 commit 0a294ac

6 files changed

Lines changed: 59 additions & 15 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ Go to `https://localhost`, and enjoy!
168168
- [Create **standalone**, self-executable PHP apps](https://frankenphp.dev/docs/embed/)
169169
- [Create static binaries](https://frankenphp.dev/docs/static/)
170170
- [Compile from sources](https://frankenphp.dev/docs/compile/)
171-
- [Monitoring FrankenPHP](https://frankenphp.dev/docs/metrics/)
171+
- [Observability](https://frankenphp.dev/docs/observability/)
172172
- [WordPress integration](https://frankenphp.dev/docs/wordpress/)
173173
- [Symfony integration](https://frankenphp.dev/docs/symfony/)
174174
- [Laravel integration](https://frankenphp.dev/docs/laravel/)

docs/logging.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Logging
22

3+
> [!TIP]
4+
> Logging is one part of FrankenPHP's observability story. See the [Observability](observability.md) page for the full picture, including real-time monitoring and metrics.
5+
36
FrankenPHP integrates seamlessly with [Caddy's logging system](https://caddyserver.com/docs/logging).
47
You can log messages using standard PHP functions or leverage the dedicated `frankenphp_log()` function for advanced
58
structured logging capabilities.

docs/metrics.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Metrics
22

3+
> [!TIP]
4+
> For a complete observability setup including real-time dashboards and production monitoring, see the [Observability](observability.md) page.
5+
36
When [Caddy metrics](https://caddyserver.com/docs/metrics) are enabled, FrankenPHP exposes the following metrics:
47

58
- `frankenphp_total_threads`: The total number of PHP threads.

docs/observability.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Observability
2+
3+
FrankenPHP provides built-in observability features: [Prometheus-compatible metrics](metrics.md) and [structured logging](logging.md).
4+
These features, combined with the recommended tools below, give you full visibility into your PHP application's behavior in development and production.
5+
6+
## Ember TUI and Prometheus Exporter
7+
8+
[Ember](https://github.com/alexandre-daubois/ember) is the most user-friendly way to monitor FrankenPHP.
9+
10+
It connects to Caddy's admin API and deeply integrates with FrankenPHP, providing real-time visibility with zero configuration and no external infrastructure.
11+
12+
It is designed to be used in development and production, with a TUI dashboard for local use and a Prometheus export daemon mode for production monitoring.
13+
14+
> [!TIP]
15+
> See the [Ember documentation](https://github.com/alexandre-daubois/ember) for the full list of features and setup details.
16+
17+
## Metrics
18+
19+
FrankenPHP exposes Prometheus-compatible metrics for threads, workers, request processing, and queue depth when [Caddy metrics](https://caddyserver.com/docs/metrics) are enabled.
20+
21+
See the [Metrics](metrics.md) page for the full list of available metrics.
22+
23+
## Logging
24+
25+
FrankenPHP integrates with Caddy's logging system and provides `frankenphp_log()` for structured logging with severity levels and context data, making ingestion into platforms like Datadog, Grafana Loki, or Elastic straightforward.
26+
27+
See the [Logging](logging.md) page for usage details.
28+
29+
## Custom Prometheus/Grafana Setup
30+
31+
If you prefer a custom monitoring stack, you can scrape FrankenPHP metrics directly.
32+
There are two options:
33+
34+
1. **Scrape Caddy directly**: Caddy exposes metrics at its admin endpoint (default: `localhost:2019/metrics`)
35+
2. **Scrape via Ember**: when running Ember with `--expose`, it exposes FrankenPHP metrics along with computed metrics derived from Caddy data (RPS, latency percentiles, error rates) on a dedicated endpoint.

scaling.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,17 @@ func initAutoScaling(mainThread *phpMainThread) {
4040
return
4141
}
4242

43+
done := mainThread.done
44+
mstate := mainThread.state
45+
4346
scalingMu.Lock()
4447
scaleChan = make(chan *frankenPHPContext)
4548
maxScaledThreads := mainThread.maxThreads - mainThread.numThreads
4649
autoScaledThreads = make([]*phpThread, 0, maxScaledThreads)
4750
scalingMu.Unlock()
4851

49-
go startUpscalingThreads(maxScaledThreads, scaleChan, mainThread.done)
50-
go startDownScalingThreads(mainThread.done)
52+
go startUpscalingThreads(maxScaledThreads, scaleChan, done, mstate)
53+
go startDownScalingThreads(done)
5154
}
5255

5356
func drainAutoScaling() {
@@ -81,16 +84,16 @@ func addWorkerThread(worker *worker) (*phpThread, error) {
8184
}
8285

8386
// scaleWorkerThread adds a worker PHP thread automatically
84-
func scaleWorkerThread(worker *worker) {
87+
func scaleWorkerThread(worker *worker, done chan struct{}, mstate *state.ThreadState) {
8588
// probe CPU usage before acquiring the lock (avoids holding lock during 120ms sleep)
86-
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) {
89+
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, done) {
8790
return
8891
}
8992

9093
scalingMu.Lock()
9194
defer scalingMu.Unlock()
9295

93-
if !mainThread.state.Is(state.Ready) {
96+
if !mstate.Is(state.Ready) {
9497
return
9598
}
9699

@@ -111,16 +114,16 @@ func scaleWorkerThread(worker *worker) {
111114
}
112115

113116
// scaleRegularThread adds a regular PHP thread automatically
114-
func scaleRegularThread() {
117+
func scaleRegularThread(done chan struct{}, mstate *state.ThreadState) {
115118
// probe CPU usage before acquiring the lock (avoids holding lock during 120ms sleep)
116-
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) {
119+
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, done) {
117120
return
118121
}
119122

120123
scalingMu.Lock()
121124
defer scalingMu.Unlock()
122125

123-
if !mainThread.state.Is(state.Ready) {
126+
if !mstate.Is(state.Ready) {
124127
return
125128
}
126129

@@ -140,7 +143,7 @@ func scaleRegularThread() {
140143
}
141144
}
142145

143-
func startUpscalingThreads(maxScaledThreads int, scale chan *frankenPHPContext, done chan struct{}) {
146+
func startUpscalingThreads(maxScaledThreads int, scale chan *frankenPHPContext, done chan struct{}, mstate *state.ThreadState) {
144147
for {
145148
scalingMu.Lock()
146149
scaledThreadCount := len(autoScaledThreads)
@@ -171,7 +174,7 @@ func startUpscalingThreads(maxScaledThreads int, scale chan *frankenPHPContext,
171174

172175
// if the request has been stalled long enough, scale
173176
if fc.worker == nil {
174-
scaleRegularThread()
177+
scaleRegularThread(done, mstate)
175178
continue
176179
}
177180

@@ -184,7 +187,7 @@ func startUpscalingThreads(maxScaledThreads int, scale chan *frankenPHPContext,
184187
continue
185188
}
186189

187-
scaleWorkerThread(fc.worker)
190+
scaleWorkerThread(fc.worker, done, mstate)
188191
case <-done:
189192
return
190193
}

scaling_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestScaleARegularThreadUpAndDown(t *testing.T) {
2020
autoScaledThread := phpThreads[1]
2121

2222
// scale up
23-
scaleRegularThread()
23+
scaleRegularThread(mainThread.done, mainThread.state)
2424
assert.Equal(t, state.Ready, autoScaledThread.state.Get())
2525
assert.IsType(t, &regularThread{}, autoScaledThread.handler)
2626

@@ -48,7 +48,7 @@ func TestScaleAWorkerThreadUpAndDown(t *testing.T) {
4848
autoScaledThread := phpThreads[2]
4949

5050
// scale up
51-
scaleWorkerThread(workersByPath[workerPath])
51+
scaleWorkerThread(workersByPath[workerPath], mainThread.done, mainThread.state)
5252
assert.Equal(t, state.Ready, autoScaledThread.state.Get())
5353

5454
// on down-scale, the thread will be marked as inactive
@@ -69,7 +69,7 @@ func TestMaxIdleTimePreventsEarlyDeactivation(t *testing.T) {
6969
autoScaledThread := phpThreads[1]
7070

7171
// scale up
72-
scaleRegularThread()
72+
scaleRegularThread(mainThread.done, mainThread.state)
7373
assert.Equal(t, state.Ready, autoScaledThread.state.Get())
7474

7575
// set wait time to 30 minutes (less than 1 hour max idle time)

0 commit comments

Comments
 (0)