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:
Родитель
4457dbf2c1
Коммит
866878e3ea
|
@ -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
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
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",
|
||||
|
|
|
@ -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)
|
||||
}
|
Загрузка…
Ссылка в новой задаче