Skip to content

Commit 3217e5d

Browse files
authored
Merge pull request #328 from xgarreau/master
fix: XHProf profiler endpoint always detected as Sentry
2 parents 9c1fa03 + 433061f commit 3217e5d

File tree

8 files changed

+100
-58
lines changed

8 files changed

+100
-58
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ php/vardumper/box.phar
66
php/vardumper/micro-*.sfx
77
php/vardumper/vardumper-parser.phar
88
modules/vardumper/bin/vardumper-parser-*
9-
/internal/frontend/dist/*
9+
/internal/frontend/dist/*
10+
fix.patch
11+
.claude/settings.local.json

internal/server/http/detect.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ func detectEventType(r *http.Request) *DetectedEvent {
3737
}
3838

3939
// Method 3: SDK-specific headers that identify the event type.
40-
if r.Header.Get("X-Sentry-Auth") != "" || strings.HasSuffix(r.URL.Path, "/envelope") || strings.HasSuffix(r.URL.Path, "/store") {
40+
isSentryStore := strings.HasSuffix(r.URL.Path, "/store") && !strings.Contains(r.URL.Path, "/profiler/")
41+
if r.Header.Get("X-Sentry-Auth") != "" || strings.HasSuffix(r.URL.Path, "/envelope") || isSentryStore {
4142
return &DetectedEvent{Type: "sentry"}
4243
}
4344
if r.Header.Get("X-Inspector-Key") != "" || r.Header.Get("X-Inspector-Version") != "" {

internal/server/http/detect_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ func TestDetectEventType(t *testing.T) {
116116
},
117117
wantType: "sentry",
118118
},
119+
{
120+
name: "profiler store path does not detect sentry",
121+
makeRequest: func() *nethttp.Request {
122+
return httptest.NewRequest("POST", "http://localhost/api/profiler/store", nil)
123+
},
124+
wantNil: true,
125+
},
119126
{
120127
name: "X-Inspector-Key header detects inspector",
121128
makeRequest: func() *nethttp.Request {

modules/profiler/api.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ func handleFlameChart(db *sql.DB) http.HandlerFunc {
265265
}
266266
}
267267

268+
visited := make(map[string]bool)
268269
var buildNode func(e EdgeRow, start float64) *flameNode
269270
buildNode = func(e EdgeRow, start float64) *flameNode {
270271
val := float64(GetEdgeMetricValue(&e, metric))
@@ -285,11 +286,15 @@ func handleFlameChart(db *sql.DB) http.HandlerFunc {
285286
Color: flameColor(pct),
286287
}
287288

288-
childStart := start
289-
for _, child := range childrenMap[e.Callee] {
290-
childNode := buildNode(child, childStart)
291-
node.Children = append(node.Children, childNode)
292-
childStart += childNode.Duration
289+
if !visited[e.Callee] {
290+
visited[e.Callee] = true
291+
childStart := start
292+
for _, child := range childrenMap[e.Callee] {
293+
childNode := buildNode(child, childStart)
294+
node.Children = append(node.Children, childNode)
295+
childStart += childNode.Duration
296+
}
297+
visited[e.Callee] = false
293298
}
294299
if node.Children == nil {
295300
node.Children = []*flameNode{}

modules/profiler/handler.go

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package profiler
22

33
import (
4-
"database/sql"
54
"encoding/json"
65
"io"
76
"net/http"
@@ -11,7 +10,6 @@ import (
1110
)
1211

1312
type handler struct {
14-
db *sql.DB
1513
}
1614

1715
func (h *handler) Priority() int { return 40 }
@@ -40,21 +38,19 @@ func (h *handler) Handle(r *http.Request) (*event.Incoming, error) {
4038
// Process the profile (compute diffs, percentages, edges).
4139
peaks, edges := Process(&incoming)
4240

43-
// Store in profiler-specific tables.
4441
uuid := event.GenerateUUID()
45-
if err := storeProfile(h.db, uuid, incoming.AppName, peaks, edges); err != nil {
46-
return nil, err
47-
}
4842

4943
// Build event payload matching PHP Buggregator format.
50-
// profile_uuid is used by the frontend to fetch call-graph/top/flame-chart.
44+
// peaks and edges are embedded so OnEventStored can store them
45+
// without re-parsing the original payload.
5146
payload := map[string]any{
5247
"profile_uuid": uuid,
5348
"app_name": incoming.AppName,
5449
"hostname": incoming.Hostname,
5550
"date": incoming.Date,
5651
"tags": incoming.Tags,
5752
"peaks": peaks,
53+
"edges": edges,
5854
"total_edges": len(edges),
5955
}
6056
b, _ := json.Marshal(payload)
@@ -65,45 +61,3 @@ func (h *handler) Handle(r *http.Request) (*event.Incoming, error) {
6561
Payload: json.RawMessage(b),
6662
}, nil
6763
}
68-
69-
func storeProfile(db *sql.DB, uuid, name string, peaks Metrics, edges map[string]Edge) error {
70-
tx, err := db.Begin()
71-
if err != nil {
72-
return err
73-
}
74-
defer tx.Rollback()
75-
76-
_, err = tx.Exec(
77-
`INSERT INTO profiles (uuid, name, cpu, wt, ct, mu, pmu) VALUES (?, ?, ?, ?, ?, ?, ?)`,
78-
uuid, name, peaks.CPU, peaks.WallTime, peaks.Calls, peaks.Memory, peaks.PeakMem,
79-
)
80-
if err != nil {
81-
return err
82-
}
83-
84-
stmt, err := tx.Prepare(`INSERT INTO profile_edges
85-
(uuid, profile_uuid, "order", cpu, wt, ct, mu, pmu, d_cpu, d_wt, d_ct, d_mu, d_pmu, p_cpu, p_wt, p_ct, p_mu, p_pmu, callee, caller, parent_uuid)
86-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
87-
if err != nil {
88-
return err
89-
}
90-
defer stmt.Close()
91-
92-
order := 0
93-
for _, edge := range edges {
94-
edgeUUID := event.GenerateUUID()
95-
_, err = stmt.Exec(
96-
edgeUUID, uuid, order,
97-
edge.Cost.CPU, edge.Cost.WallTime, edge.Cost.Calls, edge.Cost.Memory, edge.Cost.PeakMem,
98-
edge.Diff.CPU, edge.Diff.WallTime, edge.Diff.Calls, edge.Diff.Memory, edge.Diff.PeakMem,
99-
edge.Percents.CPU, edge.Percents.WallTime, edge.Percents.Calls, edge.Percents.Memory, edge.Percents.PeakMem,
100-
edge.Callee, edge.Caller, edge.Parent,
101-
)
102-
if err != nil {
103-
return err
104-
}
105-
order++
106-
}
107-
108-
return tx.Commit()
109-
}

modules/profiler/module.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package profiler
22

33
import (
44
"database/sql"
5+
"encoding/json"
6+
"log/slog"
57
"net/http"
68

79
"github.com/buggregator/go-buggregator/internal/event"
@@ -29,7 +31,7 @@ func (m *Module) OnInit(db *sql.DB) error {
2931
}
3032

3133
func (m *Module) HTTPHandler() module.HTTPIngestionHandler {
32-
return &handler{db: m.db}
34+
return &handler{}
3335
}
3436

3537
func (m *Module) RegisterRoutes(mux *http.ServeMux, store event.Store) {
@@ -39,3 +41,24 @@ func (m *Module) RegisterRoutes(mux *http.ServeMux, store event.Store) {
3941
func (m *Module) PreviewMapper() event.PreviewMapper {
4042
return &previewMapper{}
4143
}
44+
45+
func (m *Module) OnEventStored(ev event.Event) {
46+
if ev.Type != "profiler" || m.db == nil {
47+
return
48+
}
49+
50+
var payload struct {
51+
ProfileUUID string `json:"profile_uuid"`
52+
AppName string `json:"app_name"`
53+
Peaks Metrics `json:"peaks"`
54+
Edges map[string]Edge `json:"edges"`
55+
}
56+
if err := json.Unmarshal(ev.Payload, &payload); err != nil {
57+
slog.Warn("profiler: failed to parse event payload", "err", err)
58+
return
59+
}
60+
61+
if err := storeProfile(m.db, payload.ProfileUUID, payload.AppName, payload.Peaks, payload.Edges); err != nil {
62+
slog.Warn("profiler: failed to store profile", "err", err)
63+
}
64+
}

modules/profiler/store.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package profiler
2+
3+
import (
4+
"database/sql"
5+
6+
"github.com/buggregator/go-buggregator/internal/event"
7+
)
8+
9+
func storeProfile(db *sql.DB, uuid, name string, peaks Metrics, edges map[string]Edge) error {
10+
tx, err := db.Begin()
11+
if err != nil {
12+
return err
13+
}
14+
defer tx.Rollback()
15+
16+
_, err = tx.Exec(
17+
`INSERT INTO profiles (uuid, name, cpu, wt, ct, mu, pmu) VALUES (?, ?, ?, ?, ?, ?, ?)`,
18+
uuid, name, peaks.CPU, peaks.WallTime, peaks.Calls, peaks.Memory, peaks.PeakMem,
19+
)
20+
if err != nil {
21+
return err
22+
}
23+
24+
stmt, err := tx.Prepare(`INSERT INTO profile_edges
25+
(uuid, profile_uuid, "order", cpu, wt, ct, mu, pmu, d_cpu, d_wt, d_ct, d_mu, d_pmu, p_cpu, p_wt, p_ct, p_mu, p_pmu, callee, caller, parent_uuid)
26+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
27+
if err != nil {
28+
return err
29+
}
30+
defer stmt.Close()
31+
32+
order := 0
33+
for _, edge := range edges {
34+
edgeUUID := event.GenerateUUID()
35+
_, err = stmt.Exec(
36+
edgeUUID, uuid, order,
37+
edge.Cost.CPU, edge.Cost.WallTime, edge.Cost.Calls, edge.Cost.Memory, edge.Cost.PeakMem,
38+
edge.Diff.CPU, edge.Diff.WallTime, edge.Diff.Calls, edge.Diff.Memory, edge.Diff.PeakMem,
39+
edge.Percents.CPU, edge.Percents.WallTime, edge.Percents.Calls, edge.Percents.Memory, edge.Percents.PeakMem,
40+
edge.Callee, edge.Caller, edge.Parent,
41+
)
42+
if err != nil {
43+
return err
44+
}
45+
order++
46+
}
47+
48+
return tx.Commit()
49+
}

modules/sentry/handler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ func (h *handler) Match(r *http.Request) bool {
3131
return true
3232
}
3333
path := r.URL.Path
34-
return strings.HasSuffix(path, "/store") || strings.HasSuffix(path, "/envelope")
34+
isSentryStore := strings.HasSuffix(path, "/store") && !strings.Contains(path, "/profiler/")
35+
return isSentryStore || strings.HasSuffix(path, "/envelope")
3536
}
3637

3738
func (h *handler) Handle(r *http.Request) (*event.Incoming, error) {

0 commit comments

Comments
 (0)