Skip to content

Commit 866878e

Browse files
firelizzard18hyangah
authored andcommitted
internal/vscgo: convert pprof to json
Adds `vscgo dump-pprof` and `vscgo serve-pprof` to support a custom profile renderer. Ideally `dump-pprof` would be sufficient. Unfortunately, `serve-pprof` is necessary (AFAIK) to work around profiles that are too large to fit within a NodeJS Buffer (when converted to JSON). Fixes #3573. Change-Id: I0767ff02912852e0cd4bec5745c79ff50f2d3b38 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/621516 Reviewed-by: Hongxiang Jiang <[email protected]> kokoro-CI: kokoro <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]> Commit-Queue: Ethan Reesor <[email protected]>
1 parent 4457dbf commit 866878e

File tree

6 files changed

+207
-11
lines changed

6 files changed

+207
-11
lines changed

extension/go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ require (
77
github.com/google/go-cmp v0.6.0
88
)
99

10+
require github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
11+
1012
require (
1113
golang.org/x/mod v0.21.0
1214
golang.org/x/sys v0.26.0 // indirect

extension/go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
22
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
4+
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
35
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
46
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
57
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=

go.mod

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ go 1.23.1
44

55
require golang.org/x/telemetry v0.0.0-20241004145657-5eebfecbdf1f
66

7-
require (
8-
golang.org/x/sync v0.8.0 // indirect
9-
golang.org/x/sys v0.26.0 // indirect
10-
)
7+
require github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8
8+
9+
require golang.org/x/sys v0.26.0 // indirect

go.sum

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
2-
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
3-
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
4-
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1+
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
2+
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
53
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
64
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
7-
golang.org/x/telemetry v0.0.0-20240712210958-268b4a8ec2d7 h1:nU8/tAV/21mkPrCjACUeSibjhynTovgRMXc32+Y1Aec=
8-
golang.org/x/telemetry v0.0.0-20240712210958-268b4a8ec2d7/go.mod h1:amNmu/SBSm2GAF3X+9U2C0epLocdh+r5Z+7oMYO5cLM=
95
golang.org/x/telemetry v0.0.0-20241004145657-5eebfecbdf1f h1:ZYeTr2+AUYPLt6ZdXsnUUHem8NJbgmZaHisnB21BOz0=
106
golang.org/x/telemetry v0.0.0-20241004145657-5eebfecbdf1f/go.mod h1:uskmY3Y2C5OU/HAtQlc9Jq98qE2bf7H3kCPFgkab838=

internal/vscgo/main.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 The Go Authors. All rights reserved.
1+
// Copyright 2024 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

@@ -43,6 +43,18 @@ func init() {
4343
short: "increment telemetry counters",
4444
run: runIncCounters,
4545
},
46+
{
47+
usage: "dump-pprof <profile>",
48+
short: "convert a pprof profile to a JSON file",
49+
hasArgs: true,
50+
run: runPprofDump,
51+
},
52+
{
53+
usage: "serve-pprof <addr> <profile>",
54+
short: "serve a pprof profile",
55+
hasArgs: true,
56+
run: runPprofServe,
57+
},
4658
{
4759
usage: "version",
4860
short: "print version information",

internal/vscgo/pprof.go

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package vscgo
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
"log"
11+
"net"
12+
"net/http"
13+
"os"
14+
15+
"github.com/google/pprof/profile"
16+
)
17+
18+
func runPprofDump(args []string) error {
19+
if len(args) != 1 {
20+
return fmt.Errorf("usage: dump-pprof <profile>")
21+
}
22+
23+
p, err := readPprof(args[0])
24+
if err != nil {
25+
return err
26+
}
27+
28+
return json.NewEncoder(os.Stdout).Encode((*Profile)(p))
29+
}
30+
31+
func runPprofServe(args []string) error {
32+
if len(args) != 2 {
33+
return fmt.Errorf("usage: serve-pprof <addr> <profile>")
34+
}
35+
36+
l, err := net.Listen("tcp", args[0])
37+
if err != nil {
38+
return err
39+
}
40+
defer l.Close()
41+
42+
p, err := readPprof(args[1])
43+
if err != nil {
44+
return err
45+
}
46+
47+
err = json.NewEncoder(os.Stdout).Encode(map[string]any{
48+
"Listen": l.Addr(),
49+
})
50+
if err != nil {
51+
return err
52+
}
53+
54+
return http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55+
w.Header().Set("Access-Control-Allow-Origin", "*")
56+
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
57+
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
58+
59+
if r.Method == "OPTIONS" {
60+
w.WriteHeader(http.StatusOK)
61+
return
62+
}
63+
64+
w.Header().Set("Content-Type", "application/json")
65+
err := json.NewEncoder(w).Encode((*Profile)(p))
66+
if err != nil {
67+
log.Println("Error: ", err)
68+
}
69+
}))
70+
}
71+
72+
func readPprof(arg string) (*Profile, error) {
73+
f, err := os.Open(arg)
74+
if err != nil {
75+
return nil, err
76+
}
77+
defer f.Close()
78+
79+
p, err := profile.Parse(f)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
return (*Profile)(p), nil
85+
}
86+
87+
type Profile profile.Profile
88+
89+
func (p *Profile) MarshalJSON() ([]byte, error) {
90+
q := struct {
91+
SampleType []*profile.ValueType
92+
DefaultSampleType string
93+
Sample []*Sample
94+
Mapping []*profile.Mapping
95+
Location []*Location
96+
Function []*profile.Function
97+
Comments []string
98+
DropFrames string
99+
KeepFrames string
100+
TimeNanos int64
101+
DurationNanos int64
102+
PeriodType *profile.ValueType
103+
Period int64
104+
}{
105+
SampleType: p.SampleType,
106+
DefaultSampleType: p.DefaultSampleType,
107+
Sample: make([]*Sample, len(p.Sample)),
108+
Mapping: p.Mapping,
109+
Location: make([]*Location, len(p.Location)),
110+
Function: p.Function,
111+
Comments: p.Comments,
112+
DropFrames: p.DropFrames,
113+
KeepFrames: p.KeepFrames,
114+
TimeNanos: p.TimeNanos,
115+
DurationNanos: p.DurationNanos,
116+
PeriodType: p.PeriodType,
117+
Period: p.Period,
118+
}
119+
for i, s := range p.Sample {
120+
q.Sample[i] = (*Sample)(s)
121+
}
122+
for i, l := range p.Location {
123+
q.Location[i] = (*Location)(l)
124+
}
125+
return json.Marshal(q)
126+
}
127+
128+
type Sample profile.Sample
129+
130+
func (p *Sample) MarshalJSON() ([]byte, error) {
131+
q := struct {
132+
Location []uint64
133+
Value []int64
134+
Label map[string][]string
135+
NumLabel map[string][]int64
136+
NumUnit map[string][]string
137+
}{
138+
Location: make([]uint64, len(p.Location)),
139+
Value: p.Value,
140+
Label: p.Label,
141+
NumLabel: p.NumLabel,
142+
NumUnit: p.NumUnit,
143+
}
144+
for i, l := range p.Location {
145+
q.Location[i] = l.ID
146+
}
147+
return json.Marshal(q)
148+
}
149+
150+
type Location profile.Location
151+
152+
func (p *Location) MarshalJSON() ([]byte, error) {
153+
q := struct {
154+
ID uint64
155+
Mapping uint64
156+
Address uint64
157+
Line []Line
158+
IsFolded bool
159+
}{
160+
ID: p.ID,
161+
Mapping: p.Mapping.ID,
162+
Address: p.Address,
163+
Line: make([]Line, len(p.Line)),
164+
IsFolded: p.IsFolded,
165+
}
166+
for i, l := range p.Line {
167+
q.Line[i] = Line(l)
168+
}
169+
return json.Marshal(q)
170+
}
171+
172+
type Line profile.Line
173+
174+
func (p *Line) MarshalJSON() ([]byte, error) {
175+
q := struct {
176+
Function uint64
177+
Line int64
178+
Column int64
179+
}{
180+
Function: p.Function.ID,
181+
Line: p.Line,
182+
Column: p.Column,
183+
}
184+
return json.Marshal(q)
185+
}

0 commit comments

Comments
 (0)