Skip to content

Commit

Permalink
internal/vscgo: convert pprof to json
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
firelizzard18 authored and hyangah committed Oct 25, 2024
1 parent 4457dbf commit 866878e
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 11 deletions.
2 changes: 2 additions & 0 deletions extension/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions extension/go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
14 changes: 13 additions & 1 deletion internal/vscgo/main.go
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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",
Expand Down
185 changes: 185 additions & 0 deletions internal/vscgo/pprof.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 866878e

Please sign in to comment.