github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/agent/phpspy/phpspy.go (about) 1 //go:build phpspy 2 // +build phpspy 3 4 // Package phpspy is a wrapper around this library called phpspy written in Rust 5 package phpspy 6 7 // #cgo darwin LDFLAGS: -L../../../third_party/phpspy -lphpspy 8 // #cgo linux,!musl LDFLAGS: -L../../../third_party/phpspy -lphpspy -ldl -lrt 9 // #cgo linux,musl LDFLAGS: -L../../../third_party/phpspy -lphpspy 10 // #include "../../../third_party/phpspy/phpspy.h" 11 // #include <stdlib.h> 12 import "C" 13 14 import ( 15 "bytes" 16 "errors" 17 "sync" 18 "time" 19 "unsafe" 20 21 "github.com/pyroscope-io/pyroscope/pkg/agent/spy" 22 ) 23 24 // TODO: make this configurable 25 // TODO: pass lower level structures between go and rust? 26 var bufferLength = 1024 * 64 27 28 type PhpSpy struct { 29 dataBuf []byte 30 dataPtr unsafe.Pointer 31 32 errorBuf []byte 33 errorPtr unsafe.Pointer 34 35 pid int 36 } 37 38 var phpSpyInitOnce = sync.Once{} 39 40 func Start(params spy.InitParams) (spy.Spy, error) { 41 phpSpyInitOnce.Do(func() { 42 if params.PHPSpyArgs != "" { 43 params := C.CString(params.PHPSpyArgs) 44 defer C.free(unsafe.Pointer(params)) 45 C.phpspy_init_spy(params) 46 } 47 }) 48 dataBuf := make([]byte, bufferLength) 49 dataPtr := unsafe.Pointer(&dataBuf[0]) 50 51 errorBuf := make([]byte, bufferLength) 52 errorPtr := unsafe.Pointer(&errorBuf[0]) 53 54 // Sometimes phpspy doesn't initialize properly right after the process starts so we need to give it some time 55 // TODO: handle this better 56 time.Sleep(1 * time.Second) 57 58 r := C.phpspy_init_pid(C.int(params.Pid), errorPtr, C.int(bufferLength)) 59 60 if r < 0 { 61 return nil, errors.New(string(errorBuf[:-r])) 62 } 63 64 return &PhpSpy{ 65 dataPtr: dataPtr, 66 dataBuf: dataBuf, 67 errorBuf: errorBuf, 68 errorPtr: errorPtr, 69 pid: params.Pid, 70 }, nil 71 } 72 73 func (s *PhpSpy) Stop() error { 74 r := C.phpspy_cleanup(C.int(s.pid), s.errorPtr, C.int(bufferLength)) 75 if r < 0 { 76 return errors.New(string(s.errorBuf[:-r])) 77 } 78 return nil 79 } 80 81 // Snapshot calls callback function with stack-trace or error. 82 func (s *PhpSpy) Snapshot(cb func(*spy.Labels, []byte, uint64) error) error { 83 r := C.phpspy_snapshot(C.int(s.pid), s.dataPtr, C.int(bufferLength), s.errorPtr, C.int(bufferLength)) 84 if r < 0 { 85 return errors.New(string(s.errorBuf[:-r])) 86 } 87 return cb(nil, trimSemicolon(s.dataBuf[:r]), 1) 88 } 89 90 func init() { 91 spy.RegisterSpy("phpspy", Start) 92 } 93 94 func trimSemicolon(b []byte) []byte { 95 if bytes.HasSuffix(b, []byte(";")) { 96 return b[:len(b)-1] 97 } 98 return b 99 }