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 golang/vscode-go#3573.

Change-Id: I0767ff02912852e0cd4bec5745c79ff50f2d3b38
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/621516
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
kokoro-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Commit-Queue: Ethan Reesor <ethan.reesor@gmail.com>
This commit is contained in:
Ethan Reesor 2024-10-21 18:19:57 -05:00 коммит произвёл Hyang-Ah Hana Kim
Родитель 4457dbf2c1
Коммит 866878e3ea
6 изменённых файлов: 207 добавлений и 11 удалений

Просмотреть файл

@ -7,6 +7,8 @@ require (
github.com/google/go-cmp v0.6.0
)
require github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
require (
golang.org/x/mod v0.21.0
golang.org/x/sys v0.26.0 // indirect

Просмотреть файл

@ -1,5 +1,7 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=

7
go.mod
Просмотреть файл

@ -4,7 +4,6 @@ go 1.23.1
require golang.org/x/telemetry v0.0.0-20241004145657-5eebfecbdf1f
require (
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
)
require github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8
require golang.org/x/sys v0.26.0 // indirect

8
go.sum
Просмотреть файл

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

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@ -43,6 +43,18 @@ func init() {
short: "increment telemetry counters",
run: runIncCounters,
},
{
usage: "dump-pprof <profile>",
short: "convert a pprof profile to a JSON file",
hasArgs: true,
run: runPprofDump,
},
{
usage: "serve-pprof <addr> <profile>",
short: "serve a pprof profile",
hasArgs: true,
run: runPprofServe,
},
{
usage: "version",
short: "print version information",

185
internal/vscgo/pprof.go Normal file
Просмотреть файл

@ -0,0 +1,185 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vscgo
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"github.com/google/pprof/profile"
)
func runPprofDump(args []string) error {
if len(args) != 1 {
return fmt.Errorf("usage: dump-pprof <profile>")
}
p, err := readPprof(args[0])
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode((*Profile)(p))
}
func runPprofServe(args []string) error {
if len(args) != 2 {
return fmt.Errorf("usage: serve-pprof <addr> <profile>")
}
l, err := net.Listen("tcp", args[0])
if err != nil {
return err
}
defer l.Close()
p, err := readPprof(args[1])
if err != nil {
return err
}
err = json.NewEncoder(os.Stdout).Encode(map[string]any{
"Listen": l.Addr(),
})
if err != nil {
return err
}
return http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode((*Profile)(p))
if err != nil {
log.Println("Error: ", err)
}
}))
}
func readPprof(arg string) (*Profile, error) {
f, err := os.Open(arg)
if err != nil {
return nil, err
}
defer f.Close()
p, err := profile.Parse(f)
if err != nil {
return nil, err
}
return (*Profile)(p), nil
}
type Profile profile.Profile
func (p *Profile) MarshalJSON() ([]byte, error) {
q := struct {
SampleType []*profile.ValueType
DefaultSampleType string
Sample []*Sample
Mapping []*profile.Mapping
Location []*Location
Function []*profile.Function
Comments []string
DropFrames string
KeepFrames string
TimeNanos int64
DurationNanos int64
PeriodType *profile.ValueType
Period int64
}{
SampleType: p.SampleType,
DefaultSampleType: p.DefaultSampleType,
Sample: make([]*Sample, len(p.Sample)),
Mapping: p.Mapping,
Location: make([]*Location, len(p.Location)),
Function: p.Function,
Comments: p.Comments,
DropFrames: p.DropFrames,
KeepFrames: p.KeepFrames,
TimeNanos: p.TimeNanos,
DurationNanos: p.DurationNanos,
PeriodType: p.PeriodType,
Period: p.Period,
}
for i, s := range p.Sample {
q.Sample[i] = (*Sample)(s)
}
for i, l := range p.Location {
q.Location[i] = (*Location)(l)
}
return json.Marshal(q)
}
type Sample profile.Sample
func (p *Sample) MarshalJSON() ([]byte, error) {
q := struct {
Location []uint64
Value []int64
Label map[string][]string
NumLabel map[string][]int64
NumUnit map[string][]string
}{
Location: make([]uint64, len(p.Location)),
Value: p.Value,
Label: p.Label,
NumLabel: p.NumLabel,
NumUnit: p.NumUnit,
}
for i, l := range p.Location {
q.Location[i] = l.ID
}
return json.Marshal(q)
}
type Location profile.Location
func (p *Location) MarshalJSON() ([]byte, error) {
q := struct {
ID uint64
Mapping uint64
Address uint64
Line []Line
IsFolded bool
}{
ID: p.ID,
Mapping: p.Mapping.ID,
Address: p.Address,
Line: make([]Line, len(p.Line)),
IsFolded: p.IsFolded,
}
for i, l := range p.Line {
q.Line[i] = Line(l)
}
return json.Marshal(q)
}
type Line profile.Line
func (p *Line) MarshalJSON() ([]byte, error) {
q := struct {
Function uint64
Line int64
Column int64
}{
Function: p.Function.ID,
Line: p.Line,
Column: p.Column,
}
return json.Marshal(q)
}