Compare commits

...

2 Commits

Author SHA1 Message Date
a cd7f02a2c0 Merge branch 'master' of git.tuxpa.in:a/pprofweb
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-05-03 00:44:57 -05:00
a c24c2f7bb4 install 2023-05-03 00:41:35 -05:00
6 changed files with 151 additions and 1 deletions

View File

@ -9,7 +9,7 @@ RUN cd /tmp && \
FROM golang:1.19.1-bullseye AS builder
COPY . /go/src/pprofweb/
WORKDIR /go/src/pprofweb
RUN go build -o server .
RUN go build -o server ./webserver
FROM gcr.io/distroless/base-debian11:latest AS run
COPY --from=builder /go/src/pprofweb/server /pprofweb

117
cli/cli.go Normal file
View File

@ -0,0 +1,117 @@
package cli
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"strings"
)
type Context struct {
context.Context
}
var CLI struct {
Profile Profile `cmd:"" aliases:"p,prof" help:"run a profile and upload"`
Upload Upload `cmd:"" aliases:"u,up" help:"upload a file"`
}
var httpClient = &http.Client{}
type BaseArgs struct {
Remote *url.URL `name:"remote" help:"which remote pprofweb server to use" env:"PPROFWEB_REMOTE_URL" default:"https://pprof.tuxpa.in"`
}
type Profile struct {
Mode string `name:"mode" short:"m" enum:"heap,goroutine,threacreate,block,mutex,profile" help:"type of profile to run" default:"heap"`
Time int `name:"time" short:"t" help:"duration of profile in seconds"`
Url *url.URL `arg:"" help:"base url of pprof server, ala http://localhost:6060/debug/pprof"`
BaseArgs
}
func (c *Profile) Run(ctx Context) error {
req := &http.Request{}
req = req.WithContext(ctx)
req.Method = "GET"
req.URL = c.Url
req.URL = req.URL.JoinPath(c.Mode)
q := req.URL.Query()
if c.Time > 0 {
}
req.URL.RawQuery = q.Encode()
fmt.Printf("getting profile from %s...\n", req.URL)
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
res, err := doUpload(ctx, c.BaseArgs, resp.Body)
if err != nil {
return err
}
fmt.Printf("see profile at %s/%s/ \n", c.Remote, res)
return nil
}
type Upload struct {
File string `arg:"" help:"file to upload" type:"path"`
BaseArgs
}
func (c *Upload) Run(ctx Context) error {
fl, err := os.Open(c.File)
if err != nil {
return err
}
defer fl.Close()
res, err := doUpload(ctx, c.BaseArgs, fl)
if err != nil {
return err
}
fmt.Printf("see profile at %s/%s/ \n", c.Remote, res)
return nil
}
func doUpload(ctx Context, c BaseArgs, dat io.Reader) (string, error) {
fmt.Printf("uploading profile to %s...\n", c.Remote)
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "pprof.pb.gz")
if err != nil {
return "", err
}
_, err = io.Copy(part, dat)
if err != nil {
return "", err
}
err = writer.Close()
if err != nil {
return "", err
}
req, err := http.NewRequestWithContext(ctx, "POST", c.Remote.JoinPath("upload").String(), body)
if err != nil {
return "", err
}
req.Header.Set("content-type", writer.FormDataContentType())
resp, err := httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bts, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strings.ToValidUTF8(string(bts), "<22>"), nil
}

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.19
require (
gfx.cafe/util/go/generic v0.0.0-20220921131905-10a2bde53f66
github.com/alecthomas/kong v0.7.1
github.com/go-chi/chi/v5 v5.0.7
github.com/google/pprof v0.0.0-20211108044417-e9b028704de0
github.com/rs/xid v1.4.0

5
go.sum
View File

@ -40,6 +40,10 @@ gfx.cafe/util/go/generic v0.0.0-20220921131905-10a2bde53f66 h1:t6agT2M/Kl5iOnhkO
gfx.cafe/util/go/generic v0.0.0-20220921131905-10a2bde53f66/go.mod h1:0mgIMiX8+M1l4VHMaOqBaydZaztbFgblQAaEDOZpUhQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4=
github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@ -118,6 +122,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d h1:uGg2frlt3IcT7kbV6LEp5ONv4vmoO2FW4qSO+my/aoM=

27
main.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"git.tuxpa.in/a/pprofweb/cli"
"github.com/alecthomas/kong"
)
func main() {
ctx := NewCLI()
sctx, cn := signal.NotifyContext(context.Background(), os.Kill, syscall.SIGTERM, syscall.SIGILL)
defer cn()
if err := ctx.Run(cli.Context{Context: sctx}); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func NewCLI() *kong.Context {
ctx := kong.Parse(&cli.CLI)
return ctx
}