Skip to content

Commit 6ea7bcf

Browse files
committed
improves rbspy + pyspy integrations, resolves #5
1 parent 89d974b commit 6ea7bcf

File tree

10 files changed

+883
-66
lines changed

10 files changed

+883
-66
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ pkged.go
99
/Icon*
1010
/scripts/packages/
1111
.eslintcache
12+
*.pyc

pkg/agent/pyspy/pyspy.go

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ package pyspy
88
// #include "../../../third_party/rustdeps/pyspy.h"
99
import "C"
1010
import (
11-
"fmt"
11+
"errors"
1212
"unsafe"
13+
"time"
1314

1415
"github.com/pyroscope-io/pyroscope/pkg/agent/spy"
1516
)
@@ -19,48 +20,55 @@ import (
1920
var bufferLength = 1024 * 64
2021

2122
type PySpy struct {
22-
cPointer unsafe.Pointer
23-
data []byte
23+
dataPtr unsafe.Pointer
24+
dataBuf []byte
25+
26+
errorBuf []byte
27+
errorPtr unsafe.Pointer
28+
2429
pid int
2530
}
2631

2732
func Start(pid int) (spy.Spy, error) {
28-
data := make([]byte, bufferLength)
29-
cPointer := unsafe.Pointer(&data[0])
33+
dataBuf := make([]byte, bufferLength)
34+
dataPtr := unsafe.Pointer(&dataBuf[0])
35+
36+
errorBuf := make([]byte, bufferLength)
37+
errorPtr := unsafe.Pointer(&errorBuf[0])
38+
39+
// TODO: handle this better
40+
time.Sleep(1 * time.Second)
3041

31-
r := C.pyspy_init(C.int(pid))
42+
r := C.pyspy_init(C.int(pid), errorPtr, C.int(bufferLength))
3243

33-
if r == -1 {
34-
return nil, fmt.Errorf("buffer too small, current size %d", bufferLength)
44+
if r < 0 {
45+
return nil, errors.New(string(errorBuf[:-r]))
3546
}
3647

3748
return &PySpy{
38-
cPointer: cPointer,
39-
data: data,
49+
dataPtr: dataPtr,
50+
dataBuf: dataBuf,
51+
errorBuf: errorBuf,
52+
errorPtr: errorPtr,
4053
pid: pid,
4154
}, nil
4255
}
4356

4457
func (s *PySpy) Stop() error {
45-
r := C.pyspy_cleanup(C.int(s.pid))
46-
if r == -1 {
47-
return fmt.Errorf("failed to close spy")
58+
r := C.pyspy_cleanup(C.int(s.pid), s.errorPtr, C.int(bufferLength))
59+
if r < 0 {
60+
return errors.New(string(s.errorBuf[:-r]))
4861
}
4962
return nil
5063
}
5164

5265
// Snapshot calls callback function with stack-trace or error.
5366
func (s *PySpy) Snapshot(cb func([]byte, error)) {
54-
newL := C.pyspy_snapshot(C.int(s.pid), s.cPointer, C.int(bufferLength))
55-
switch newL {
56-
case -1:
57-
cb(nil, fmt.Errorf("buffer too small, current size %d", bufferLength))
58-
case -2:
59-
cb(nil, fmt.Errorf("spy is not initialized yet"))
60-
case -3:
61-
cb(nil, fmt.Errorf("failed to get a trace"))
62-
default:
63-
cb(s.data[:newL], nil)
67+
r := C.pyspy_snapshot(C.int(s.pid), s.dataPtr, C.int(bufferLength), s.errorPtr, C.int(bufferLength))
68+
if r < 0 {
69+
cb(nil, errors.New(string(s.errorBuf[:-r])))
70+
} else {
71+
cb(s.dataBuf[:r], nil)
6472
}
6573
}
6674

pkg/agent/rbspy/rbspy.go

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package rbspy
88
// #include "../../../third_party/rustdeps/rbspy.h"
99
import "C"
1010
import (
11-
"fmt"
11+
"errors"
1212
"unsafe"
1313

1414
"github.com/pyroscope-io/pyroscope/pkg/agent/spy"
@@ -19,48 +19,52 @@ import (
1919
var bufferLength = 1024 * 64
2020

2121
type RbSpy struct {
22-
cPointer unsafe.Pointer
23-
data []byte
24-
pid int
22+
dataBuf []byte
23+
dataPtr unsafe.Pointer
24+
25+
errorBuf []byte
26+
errorPtr unsafe.Pointer
27+
28+
pid int
2529
}
2630

2731
func Start(pid int) (spy.Spy, error) {
28-
data := make([]byte, bufferLength)
29-
cPointer := unsafe.Pointer(&data[0])
32+
dataBuf := make([]byte, bufferLength)
33+
dataPtr := unsafe.Pointer(&dataBuf[0])
34+
35+
errorBuf := make([]byte, bufferLength)
36+
errorPtr := unsafe.Pointer(&errorBuf[0])
3037

31-
r := C.rbspy_init(C.int(pid))
38+
r := C.rbspy_init(C.int(pid), errorPtr, C.int(bufferLength))
3239

33-
if r == -1 {
34-
return nil, fmt.Errorf("buffer too small, current size %d", bufferLength)
40+
if r < 0 {
41+
return nil, errors.New(string(errorBuf[:-r]))
3542
}
3643

3744
return &RbSpy{
38-
cPointer: cPointer,
39-
data: data,
45+
dataPtr: dataPtr,
46+
dataBuf: dataBuf,
47+
errorBuf: errorBuf,
48+
errorPtr: errorPtr,
4049
pid: pid,
4150
}, nil
4251
}
4352

4453
func (s *RbSpy) Stop() error {
45-
r := C.rbspy_cleanup(C.int(s.pid))
46-
if r == -1 {
47-
return fmt.Errorf("failed to close spy")
54+
r := C.rbspy_cleanup(C.int(s.pid), s.errorPtr, C.int(bufferLength))
55+
if r < 0 {
56+
return errors.New(string(s.errorBuf[:-r]))
4857
}
4958
return nil
5059
}
5160

5261
// Snapshot calls callback function with stack-trace or error.
5362
func (s *RbSpy) Snapshot(cb func([]byte, error)) {
54-
newL := C.rbspy_snapshot(C.int(s.pid), s.cPointer, C.int(bufferLength))
55-
switch newL {
56-
case -1:
57-
cb(nil, fmt.Errorf("buffer too small, current size %d", bufferLength))
58-
case -2:
59-
cb(nil, fmt.Errorf("spy is not initialized yet"))
60-
case -3:
61-
cb(nil, fmt.Errorf("failed to get a trace"))
62-
default:
63-
cb(s.data[:newL], nil)
63+
r := C.rbspy_snapshot(C.int(s.pid), s.dataPtr, C.int(bufferLength), s.errorPtr, C.int(bufferLength))
64+
if r < 0 {
65+
cb(nil, errors.New(string(s.errorBuf[:-r])))
66+
} else {
67+
cb(s.dataBuf[:r], nil)
6468
}
6569
}
6670

pkg/agent/session.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ func (ps *ProfileSession) Stop() {
134134
}
135135
close(ps.stopCh)
136136

137-
now := time.Now()
138-
ps.upstream.Upload(ps.appName, ps.startTime, now, ps.spyName, ps.sampleRate, ps.trie)
137+
if ps.trie != nil {
138+
ps.upstream.Upload(ps.appName, ps.startTime, time.Now(), ps.spyName, ps.sampleRate, ps.trie)
139+
}
139140
}
140141

141142
func (ps *ProfileSession) addSubprocesses() {

pkg/exec/cli.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77
"os/exec"
8-
"os/signal"
98
"os/user"
109
"path"
1110
"runtime"
@@ -20,6 +19,7 @@ import (
2019
"github.com/pyroscope-io/pyroscope/pkg/agent/upstream/remote"
2120
"github.com/pyroscope-io/pyroscope/pkg/config"
2221
"github.com/pyroscope-io/pyroscope/pkg/util/atexit"
22+
"github.com/pyroscope-io/pyroscope/pkg/util/names"
2323
"github.com/sirupsen/logrus"
2424
)
2525

@@ -53,8 +53,15 @@ func Cli(cfg *config.Config, args []string) error {
5353
return err
5454
}
5555

56-
signal.Ignore(syscall.SIGCHLD)
56+
if cfg.Exec.ApplicationName == "" {
57+
logrus.Infof("we recommend specifying application name via %s flag or env variable %s", color.YellowString("-application-name"), color.YellowString("PYROSCOPE_APPLICATION_NAME"))
58+
cfg.Exec.ApplicationName = spyName + "." + names.GetRandomName(generateSeed(args))
59+
logrus.Infof("for now we chose the name for you and it's \"%s\"", color.BlueString(cfg.Exec.ApplicationName))
60+
}
5761

62+
logrus.WithFields(logrus.Fields{
63+
"args": fmt.Sprintf("%q", args),
64+
}).Debug("starting command")
5865
cmd := exec.Command(args[0], args[1:]...)
5966
cmd.Stderr = os.Stderr
6067
cmd.Stdout = os.Stdout
@@ -72,12 +79,19 @@ func Cli(cfg *config.Config, args []string) error {
7279
})
7380
defer u.Stop()
7481

75-
// TODO: improve this logic, basically we need a smart way of detecting that an app successfully loaded.
76-
// Maybe do this on some ticker (every 100 ms) with a timeout (20 s). Make this configurable too
77-
time.Sleep(5 * time.Second)
82+
logrus.WithFields(logrus.Fields{
83+
"app-name": cfg.Exec.ApplicationName,
84+
"spy-name": spyName,
85+
"pid": cmd.Process.Pid,
86+
"detect-subprocesses": cfg.Exec.DetectSubprocesses,
87+
}).Debug("starting agent session")
88+
7889
// TODO: add sample rate, make it configurable
7990
sess := agent.NewSession(u, cfg.Exec.ApplicationName, spyName, 100, cmd.Process.Pid, cfg.Exec.DetectSubprocesses)
80-
sess.Start()
91+
err = sess.Start()
92+
if err != nil {
93+
logrus.Errorf("error when starting session: %q", err)
94+
}
8195
defer sess.Stop()
8296

8397
waitForProcessToExit(cmd)
@@ -89,6 +103,10 @@ func Cli(cfg *config.Config, args []string) error {
89103
func waitForProcessToExit(cmd *exec.Cmd) {
90104
sigc := make(chan struct{})
91105

106+
go func(){
107+
cmd.Wait()
108+
}()
109+
92110
atexit.Register(func() {
93111
sigc <- struct{}{}
94112
})
@@ -97,11 +115,13 @@ func waitForProcessToExit(cmd *exec.Cmd) {
97115
for {
98116
select {
99117
case <-sigc:
118+
logrus.Debug("received a signal, killing subprocess")
100119
cmd.Process.Kill()
101120
return
102121
case <-t.C:
103122
p, err := ps.FindProcess(cmd.Process.Pid)
104123
if p == nil || err != nil {
124+
logrus.WithField("err", err).Debug("could not find subprocess, it might be dead")
105125
return
106126
}
107127
}
@@ -152,3 +172,11 @@ func armMessage() string {
152172
}
153173
return ""
154174
}
175+
176+
func generateSeed(args []string) string{
177+
path, err := os.Getwd()
178+
if err != nil {
179+
path = "<unknown>"
180+
}
181+
return path + "|" + strings.Join(args, "&")
182+
}

0 commit comments

Comments
 (0)