Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support profiling #250

Merged
merged 2 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ linters:
- exhaustive
- exhaustruct
- forcetypeassert
- funlen
- gochecknoglobals
- gochecknoinits
- goerr113
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,34 @@ This flag sets tag key value pairs to set on all stats. This flag can be specifi

Example: `--stats.tags="app=my-app" --stats.tags="zone=eu-west"`

### Profiler

The profiler flags are used by `cmd.NewProfiler` to create a Pyroscope `*pyroscope.Profiler`.

#### FlagProfilingDSN: *--profiling.dsn, $PROFILING_DSN*

This flag configures the URL, authentication and optionally the Tenant ID for Pyroscope.

Example: `--profiling.dsn=https://user:pass@host/path?token=auth-token&tenantid=my-tenant-id`

#### FlagProfileUploadRate: *--profiling.upload-rate, $PROFILING_UPLOAD_RATE*

This flag configures the rate at which profiles are uploaded.

Example: `--profiling.upload-rate=10s`

#### FlagProfilingTags: *--profiling.tags, $PROFILING_TAGS*

This configures a list of tags appended to every profile. This flag can be specified multiple times.

Example: `--profiling.tags="app=my-app" --profiling.tags="zone=eu-west"`

#### FlagProfilingTypes: *--profiling.types, $PROFILING_TYPES*

This configures the profile types that are captured. By default all supported types are captured. This flag can be specified multiple times.

Example: `--profiling.types=cpu --profiling.types=inuse_object`

### Tracer

The tracing flags are used by `cmd.NewTracer` to create a new open telemetry `trace.TraceProvider`.
Expand Down
21 changes: 21 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ func ExampleNewStatter() {
_ = stats
}

func ExampleNewProfiler() {
var c *cli.Context // Get this from your action

log, err := cmd.NewLogger(c)
if err != nil {
// Handle error.
return
}

prof, err := cmd.NewProfiler(c, "my-service", log)
if err != nil {
// Handle error.
return
}
if prof != nil {
defer func() { _ = prof.Stop() }()
}

_ = prof
}

func ExampleNewTracer() {
var c *cli.Context // Get this from your action

Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.21

require (
github.com/fatih/color v1.16.0
github.com/grafana/otel-profiling-go v0.5.1
github.com/grafana/pyroscope-go v1.1.1
github.com/hamba/logger/v2 v2.5.1
github.com/hamba/statter/v2 v2.3.5
github.com/stretchr/testify v1.9.0
Expand All @@ -28,7 +30,9 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/klauspost/compress v1.17.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
Expand Down
22 changes: 22 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
Expand All @@ -27,13 +29,21 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ=
github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88=
github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo=
github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/hamba/logger/v2 v2.5.1 h1:EM+S6CPYIs66XmW9kK/RBghgtkLcO4kQWWPAlsIwNR4=
github.com/hamba/logger/v2 v2.5.1/go.mod h1:b+y7XddZMTTSIjKdOOIHWlhg1hMoE9eKhanB9wPNLj0=
github.com/hamba/statter/v2 v2.3.5 h1:qAFu+n4Q08LtrBdcBYexItfOoJJ16D/m/boq8fm5hn4=
github.com/hamba/statter/v2 v2.3.5/go.mod h1:LS7DqETxoVpRraL3tn9O70zYCaVRL2A98SvZw/LZkAI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down Expand Up @@ -61,8 +71,14 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
Expand All @@ -73,6 +89,7 @@ github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OL
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
Expand All @@ -83,10 +100,13 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
Expand All @@ -97,6 +117,7 @@ golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
Expand All @@ -117,5 +138,6 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
142 changes: 142 additions & 0 deletions profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package cmd

import (
"errors"
"fmt"
"net/url"
"time"

"github.com/grafana/pyroscope-go"
"github.com/hamba/logger/v2"
"github.com/urfave/cli/v2"
)

var allProfilingTypes = []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileInuseSpace,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileGoroutines,
pyroscope.ProfileMutexCount,
pyroscope.ProfileMutexDuration,
pyroscope.ProfileBlockCount,
pyroscope.ProfileBlockDuration,
}

// Tracing flag constants declared for CLI use.
const (
FlagProfilingDSN = "profiling.dsn"
FlagProfileUploadRate = "profiling.upload-rate"
FlagProfilingTags = "profiling.tags"
FlagProfilingTypes = "profiling.types"
)

// ProfilingFlags are flags that configure profiling.
var ProfilingFlags = Flags{
&cli.StringFlag{
Name: FlagProfilingDSN,
Usage: "The address to the Pyroscope server, in the format " +
"'http://basic:auth@server:port?token=auth-token&tenantid=tenant-id'.",
EnvVars: []string{"PROFILING_DSN"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use strcase.ToSNAKE ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current version doesn't at the moment, but the next major will.

},
&cli.DurationFlag{
Name: FlagProfileUploadRate,
Usage: "The rate at which profiles are uploaded.",
Value: 15 * time.Second,
EnvVars: []string{"PROFILING_UPLOAD_RATE"},
},
&cli.StringSliceFlag{
Name: FlagProfilingTags,
Usage: "A list of tags appended to every profile. Format: key=value.",
EnvVars: []string{"PROFILING_TAGS"},
},
&cli.StringSliceFlag{
Name: FlagProfilingTypes,
Usage: "The type of profiles to include. Defaults to all.",
EnvVars: []string{"PROFILING_TYPES"},
},
}

// NewProfiler returns a profiler configured from the cli.
// If no profiler is configured, nil is returned.
func NewProfiler(c *cli.Context, svc string, log *logger.Logger) (*pyroscope.Profiler, error) {
dsn := c.String(FlagProfilingDSN)
if dsn == "" {
//nolint:nilnil // There is no sentinel in this case.
return nil, nil
}

u, err := url.Parse(dsn)
if err != nil {
return nil, fmt.Errorf("parsing profiling DSN: %w", err)
}

tenantID := u.Query().Get("tenantid")

authToken := u.Query().Get("token")
var username, password string
if u.User != nil {
username = u.User.Username()
password, _ = u.User.Password()
}
if (username != "" || password != "") && authToken != "" {
return nil, errors.New("cannot set auth token and basic auth")
}

srvURL := &url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: u.Path,
}

tags := map[string]string{}
if pairs := c.StringSlice(FlagProfilingTags); len(pairs) > 0 {
strTags, err := Split(pairs, "=")
if err != nil {
return nil, err
}
for _, kv := range strTags {
tags[kv[0]] = kv[1]
}
}

types := allProfilingTypes
if newTypes := c.StringSlice(FlagProfilingTypes); len(newTypes) > 0 {
types = make([]pyroscope.ProfileType, len(newTypes))
for i, typ := range newTypes {
types[i] = pyroscope.ProfileType(typ)
}
}

cfg := pyroscope.Config{
ApplicationName: svc,
Tags: tags,
ServerAddress: srvURL.String(),
AuthToken: authToken,
BasicAuthUser: username,
BasicAuthPassword: password,
TenantID: tenantID,
UploadRate: c.Duration(FlagProfileUploadRate),
Logger: pyroLogAdapter{log: log},
ProfileTypes: types,
}

return pyroscope.Start(cfg)
}

type pyroLogAdapter struct {
log *logger.Logger
}

func (a pyroLogAdapter) Infof(format string, args ...any) {
a.log.Info(fmt.Sprintf(format, args...))
}

func (a pyroLogAdapter) Debugf(format string, args ...any) {
a.log.Debug(fmt.Sprintf(format, args...))
}

func (a pyroLogAdapter) Errorf(format string, args ...any) {
a.log.Error(fmt.Sprintf(format, args...))
}
Loading
Loading