Skip to content

Commit 0ebcc00

Browse files
feat(debug): add more metrics in ThreadDebugState
1 parent 33fcc4d commit 0ebcc00

File tree

6 files changed

+99
-8
lines changed

6 files changed

+99
-8
lines changed

caddy/admin_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,46 @@ func TestShowTheCorrectThreadDebugStatus(t *testing.T) {
8282
assert.Len(t, debugState.ThreadDebugStates, 3)
8383
}
8484

85+
func TestThreadDebugStateMetricsAfterRequests(t *testing.T) {
86+
tester := caddytest.NewTester(t)
87+
tester.InitServer(`
88+
{
89+
skip_install_trust
90+
admin localhost:2999
91+
http_port `+testPort+`
92+
93+
frankenphp {
94+
num_threads 2
95+
worker ../testdata/worker-with-counter.php 1
96+
}
97+
}
98+
99+
localhost:`+testPort+` {
100+
route {
101+
root ../testdata
102+
rewrite worker-with-counter.php
103+
php
104+
}
105+
}
106+
`, "caddyfile")
107+
108+
// make a few requests so counters are populated
109+
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:1")
110+
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:2")
111+
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:3")
112+
113+
debugState := getDebugState(t, tester)
114+
115+
hasRequestCount := false
116+
for _, ts := range debugState.ThreadDebugStates {
117+
if ts.RequestCount > 0 {
118+
hasRequestCount = true
119+
assert.Greater(t, ts.MemoryUsage, int64(0), "thread %d (%s) should report memory usage", ts.Index, ts.Name)
120+
}
121+
}
122+
assert.True(t, hasRequestCount, "at least one thread should have RequestCount > 0 after serving requests")
123+
}
124+
85125
func TestAutoScaleWorkerThreads(t *testing.T) {
86126
wg := sync.WaitGroup{}
87127
maxTries := 10

debugstate.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ type ThreadDebugState struct {
1212
IsWaiting bool
1313
IsBusy bool
1414
WaitingSinceMilliseconds int64
15+
CurrentURI string
16+
CurrentMethod string
17+
RequestStartedAt int64
18+
RequestCount int64
19+
MemoryUsage int64
1520
}
1621

1722
// EXPERIMENTAL: FrankenPHPDebugState prints the state of all PHP threads - debugging purposes only
@@ -39,12 +44,38 @@ func DebugState() FrankenPHPDebugState {
3944

4045
// threadDebugState creates a small jsonable status message for debugging purposes
4146
func threadDebugState(thread *phpThread) ThreadDebugState {
42-
return ThreadDebugState{
47+
isBusy := !thread.state.IsInWaitingState()
48+
49+
s := ThreadDebugState{
4350
Index: thread.threadIndex,
4451
Name: thread.name(),
4552
State: thread.state.Name(),
4653
IsWaiting: thread.state.IsInWaitingState(),
47-
IsBusy: !thread.state.IsInWaitingState(),
54+
IsBusy: isBusy,
4855
WaitingSinceMilliseconds: thread.state.WaitTime(),
4956
}
57+
58+
s.RequestCount = thread.requestCount.Load()
59+
s.MemoryUsage = thread.lastMemoryUsage.Load()
60+
61+
if isBusy {
62+
thread.handlerMu.RLock()
63+
fc := thread.handler.frankenPHPContext()
64+
thread.handlerMu.RUnlock()
65+
66+
if fc != nil && fc.request != nil && fc.responseWriter != nil {
67+
if fc.originalRequest != nil {
68+
s.CurrentURI = fc.originalRequest.URL.RequestURI()
69+
s.CurrentMethod = fc.originalRequest.Method
70+
} else {
71+
s.CurrentURI = fc.requestURI
72+
s.CurrentMethod = fc.request.Method
73+
}
74+
if !fc.startedAt.IsZero() {
75+
s.RequestStartedAt = fc.startedAt.UnixMilli()
76+
}
77+
}
78+
}
79+
80+
return s
5081
}

frankenphp.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,12 @@ static void frankenphp_reset_session_state(void) {
244244
}
245245
#endif
246246

247+
static __thread size_t thread_last_memory_usage = 0;
248+
247249
/* Adapted from php_request_shutdown */
248250
static void frankenphp_worker_request_shutdown() {
251+
thread_last_memory_usage = zend_memory_usage(0);
252+
249253
/* Flush all output buffers */
250254
zend_try { php_output_end_all(); }
251255
zend_end_try();
@@ -1233,6 +1237,7 @@ int frankenphp_execute_script(char *file_name) {
12331237
sandboxed_env = NULL;
12341238
}
12351239

1240+
thread_last_memory_usage = zend_memory_usage(0);
12361241
php_request_shutdown((void *)0);
12371242
frankenphp_free_request_context();
12381243

@@ -1405,6 +1410,10 @@ int frankenphp_reset_opcache(void) {
14051410

14061411
int frankenphp_get_current_memory_limit() { return PG(memory_limit); }
14071412

1413+
size_t frankenphp_get_current_memory_usage() {
1414+
return thread_last_memory_usage;
1415+
}
1416+
14081417
static zend_module_entry **modules = NULL;
14091418
static int modules_len = 0;
14101419
static int (*original_php_register_internal_extensions_func)(void) = NULL;

frankenphp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ void frankenphp_register_server_vars(zval *track_vars_array,
185185
zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
186186
int frankenphp_reset_opcache(void);
187187
int frankenphp_get_current_memory_limit();
188+
size_t frankenphp_get_current_memory_usage();
188189

189190
void register_extensions(zend_module_entry **m, int len);
190191

phpthread.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"runtime"
99
"sync"
10+
"sync/atomic"
1011
"unsafe"
1112

1213
"github.com/dunglas/frankenphp/internal/state"
@@ -16,12 +17,14 @@ import (
1617
// identified by the index in the phpThreads slice
1718
type phpThread struct {
1819
runtime.Pinner
19-
threadIndex int
20-
requestChan chan contextHolder
21-
drainChan chan struct{}
22-
handlerMu sync.RWMutex
23-
handler threadHandler
24-
state *state.ThreadState
20+
threadIndex int
21+
requestChan chan contextHolder
22+
drainChan chan struct{}
23+
handlerMu sync.RWMutex
24+
handler threadHandler
25+
state *state.ThreadState
26+
requestCount atomic.Int64
27+
lastMemoryUsage atomic.Int64
2528
}
2629

2730
// threadHandler defines how the callbacks from the C thread should be handled
@@ -173,6 +176,10 @@ func go_frankenphp_after_script_execution(threadIndex C.uintptr_t, exitStatus C.
173176
if exitStatus < 0 {
174177
panic(ErrScriptExecution)
175178
}
179+
180+
thread.requestCount.Add(1)
181+
thread.lastMemoryUsage.Store(int64(C.frankenphp_get_current_memory_usage()))
182+
176183
thread.handler.afterScriptExecution(int(exitStatus))
177184

178185
// unpin all memory used during script execution

threadworker.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ func go_frankenphp_finish_worker_request(threadIndex C.uintptr_t, retval *C.zval
292292
fc.handlerReturn = r
293293
}
294294

295+
thread.requestCount.Add(1)
296+
thread.lastMemoryUsage.Store(int64(C.frankenphp_get_current_memory_usage()))
297+
295298
fc.closeContext()
296299
thread.handler.(*workerThread).workerFrankenPHPContext = nil
297300
thread.handler.(*workerThread).workerContext = nil

0 commit comments

Comments
 (0)