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  }