Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion caddy/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"slices"
"strconv"
"strings"
"time"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
Expand Down Expand Up @@ -235,13 +236,64 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c

// TODO: set caddyhttp.ServerHeader when https://github.com/caddyserver/caddy/pull/7338 will be released
w.Header()["Server"] = serverHeader
if err = frankenphp.ServeHTTP(w, fr); err != nil && !errors.As(err, &frankenphp.ErrRejected{}) {
err = frankenphp.ServeHTTP(w, fr)

// Retrieve and add stats to the Caddy Replacer
stats, ok := frankenphp.StatusFromContext(fr.Context())
if ok {
repl.Map(func(key string) (any, bool) {
switch key {
case "http.frankenphp.cpu_usage":
return stats.CpuUsage.Nanoseconds(), true
case "http.frankenphp.cpu_usage_human":
return stats.CpuUsage.String(), true
case "http.frankenphp.memory_usage":
return stats.MemoryUsage, true
case "http.frankenphp.memory_usage_human":
return formatBytes(stats.MemoryUsage), true
case "http.frankenphp.script":
return stats.Script, true
case "http.frankenphp.script_filename":
return stats.ScriptFilename, true
default:
return "", false
}
})
}

if err != nil && !errors.As(err, &frankenphp.ErrRejected{}) {
return caddyhttp.Error(http.StatusInternalServerError, err)
}

return nil
}

func formatDuration(d time.Duration) string {
if d.Hours() >= 1 {
return fmt.Sprintf("%.2f h", d.Hours())
}
if d.Minutes() >= 1 {
return fmt.Sprintf("%.2f m", d.Minutes())
}
if d.Seconds() >= 1 {
return fmt.Sprintf("%.2f s", d.Seconds())
}
return fmt.Sprintf("%d ms", d.Milliseconds())
}

func formatBytes(b uint64) string {
if (b >> 30) > 0 {
return fmt.Sprintf("%.2f GB", float64(b)/float64(1<<30))
}
if (b >> 20) > 0 {
return fmt.Sprintf("%.2f MB", float64(b)/float64(1<<20))
}
if (b >> 10) > 0 {
return fmt.Sprintf("%.2f KB", float64(b)/float64(1<<10))
}
return fmt.Sprintf("%d B", b)
}

// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
Expand Down
2 changes: 2 additions & 0 deletions cgi.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info)
info.request_uri = thread.pinCString(request.URL.RequestURI())

info.proto_num = C.int(request.ProtoMajor*1000 + request.ProtoMinor)

C.clock_gettime(C.CLOCK_THREAD_CPUTIME_ID, &thread.cpuRequestStartTime)
}

// SanitizedPathJoin performs filepath.Join(root, reqPath) that
Expand Down
10 changes: 10 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ type frankenPHPContext struct {

done chan any
startedAt time.Time

memoryUsage uint64
cpuUsage time.Duration
}

type Stats struct {
MemoryUsage uint64
CpuUsage time.Duration
Script string
ScriptFilename string
}

type contextHolder struct {
Expand Down
5 changes: 5 additions & 0 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,9 @@ PHP_FUNCTION(frankenphp_handle_request) {
fci.params = result.r1;
fci.param_count = result.r1 == NULL ? 0 : 1;

// Reset memory peak usage at the start of the request
zend_memory_reset_peak_usage();

if (zend_call_function(&fci, &fcc) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
callback_ret = &retval;
}
Expand Down Expand Up @@ -609,6 +612,8 @@ static zend_module_entry frankenphp_module = {
STANDARD_MODULE_PROPERTIES};

static void frankenphp_request_shutdown() {
go_frankenphp_set_memory_peak_usage(thread_index, zend_memory_peak_usage(1));

frankenphp_free_request_context();
php_request_shutdown((void *)0);
}
Expand Down
23 changes: 23 additions & 0 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,29 @@ func Shutdown() {
resetGlobals()
}

// Stats returns FrankenPHP-specific execution stats from the given context.
func StatusFromContext(ctx context.Context) (Stats, bool) {
fc, ok := fromContext(ctx)
if !ok {
return Stats{}, false
}
if fc.worker != nil {
return Stats{
CpuUsage: fc.cpuUsage,
MemoryUsage: fc.memoryUsage,
Script: fc.worker.fileName,
ScriptFilename: fc.worker.fileName,
}, true
} else {
return Stats{
CpuUsage: fc.cpuUsage,
MemoryUsage: fc.memoryUsage,
Script: fc.scriptName,
ScriptFilename: fc.scriptFilename,
}, true
}
}

// ServeHTTP executes a PHP script according to the given context.
func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error {
h := responseWriter.Header()
Expand Down
34 changes: 27 additions & 7 deletions phpthread.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"runtime"
"sync"
"time"
"unsafe"

"github.com/dunglas/frankenphp/internal/state"
Expand All @@ -16,13 +17,16 @@ import (
// identified by the index in the phpThreads slice
type phpThread struct {
runtime.Pinner
threadIndex int
requestChan chan contextHolder
drainChan chan struct{}
handlerMu sync.Mutex
handler threadHandler
state *state.ThreadState
sandboxedEnv map[string]*C.zend_string
threadIndex int
requestChan chan contextHolder
drainChan chan struct{}
handlerMu sync.Mutex
handler threadHandler
state *state.ThreadState
sandboxedEnv map[string]*C.zend_string
cpuRequestStartTime C.struct_timespec
lastRequestCpuUsage time.Duration
lastRequestMemoryUsage uint64
}

// interface that defines how the callbacks from the C thread should be handled
Expand Down Expand Up @@ -147,6 +151,12 @@ func (*phpThread) updateContext(isWorker bool) {
C.frankenphp_update_local_thread_context(C.bool(isWorker))
}

func (thread *phpThread) requestCpuUsage() time.Duration {
var cpuEndTime C.struct_timespec
C.clock_gettime(C.CLOCK_THREAD_CPUTIME_ID, &cpuEndTime)
return time.Duration(float64(cpuEndTime.tv_sec-thread.cpuRequestStartTime.tv_sec)*1e9 + float64(cpuEndTime.tv_nsec-thread.cpuRequestStartTime.tv_nsec))
}

//export go_frankenphp_before_script_execution
func go_frankenphp_before_script_execution(threadIndex C.uintptr_t) *C.char {
thread := phpThreads[threadIndex]
Expand All @@ -167,12 +177,22 @@ func go_frankenphp_after_script_execution(threadIndex C.uintptr_t, exitStatus C.
if exitStatus < 0 {
panic(ErrScriptExecution)
}

// Calculate CPU usage
thread.lastRequestCpuUsage = thread.requestCpuUsage()

thread.handler.afterScriptExecution(int(exitStatus))

// unpin all memory used during script execution
thread.Unpin()
}

//export go_frankenphp_set_memory_peak_usage
func go_frankenphp_set_memory_peak_usage(threadIndex C.uintptr_t, memoryPeakUsage C.size_t) {
thread := phpThreads[threadIndex]
thread.lastRequestMemoryUsage = uint64(memoryPeakUsage)
}

//export go_frankenphp_on_thread_shutdown
func go_frankenphp_on_thread_shutdown(threadIndex C.uintptr_t) {
thread := phpThreads[threadIndex]
Expand Down
2 changes: 2 additions & 0 deletions threadregular.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ func (handler *regularThread) waitForRequest() string {
}

func (handler *regularThread) afterRequest() {
handler.contextHolder.frankenPHPContext.cpuUsage = handler.thread.lastRequestCpuUsage
handler.contextHolder.frankenPHPContext.memoryUsage = handler.thread.lastRequestMemoryUsage
handler.contextHolder.frankenPHPContext.closeContext()
handler.contextHolder.frankenPHPContext = nil
handler.ctx = nil
Expand Down
6 changes: 6 additions & 0 deletions threadworker.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ func go_frankenphp_finish_worker_request(threadIndex C.uintptr_t, retval *C.zval
ctx := thread.context()
fc := ctx.Value(contextKey).(*frankenPHPContext)

// Stats
thread.lastRequestCpuUsage = thread.requestCpuUsage()
thread.lastRequestMemoryUsage = uint64(C.zend_memory_peak_usage(C.bool(true)))
fc.cpuUsage = thread.lastRequestCpuUsage
fc.memoryUsage = thread.lastRequestMemoryUsage

if retval != nil {
r, err := GoValue[any](unsafe.Pointer(retval))
if err != nil && globalLogger.Enabled(ctx, slog.LevelError) {
Expand Down
Loading