diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3344e591..51a90851 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -80,4 +80,10 @@ updates: - "๐Ÿค– Dependencies" schedule: interval: "daily" + - package-ecosystem: "gomod" + directory: "/fgprof" # Location of package manifests + labels: + - "๐Ÿค– Dependencies" + schedule: + interval: "daily" diff --git a/.github/release-drafter-fgprof.yml b/.github/release-drafter-fgprof.yml new file mode 100644 index 00000000..c2c9cf21 --- /dev/null +++ b/.github/release-drafter-fgprof.yml @@ -0,0 +1,50 @@ +name-template: 'Fgprof - v$RESOLVED_VERSION' +tag-template: 'fgprof/v$RESOLVED_VERSION' +tag-prefix: fgprof/v +include-paths: + - fgprof +categories: + - title: 'โ— Breaking Changes' + labels: + - 'โ— BreakingChange' + - title: '๐Ÿš€ New' + labels: + - 'โœ๏ธ Feature' + - title: '๐Ÿงน Updates' + labels: + - '๐Ÿงน Updates' + - '๐Ÿค– Dependencies' + - title: '๐Ÿ› Fixes' + labels: + - 'โ˜ข๏ธ Bug' + - title: '๐Ÿ“š Documentation' + labels: + - '๐Ÿ“’ Documentation' +change-template: '- $TITLE (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +exclude-contributors: + - dependabot + - dependabot[bot] +version-resolver: + major: + labels: + - 'major' + - 'โ— BreakingChange' + minor: + labels: + - 'minor' + - 'โœ๏ธ Feature' + patch: + labels: + - 'patch' + - '๐Ÿ“’ Documentation' + - 'โ˜ข๏ธ Bug' + - '๐Ÿค– Dependencies' + - '๐Ÿงน Updates' + default: patch +template: | + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...fgprof/v$RESOLVED_VERSION + + Thank you $CONTRIBUTORS for making this update possible. diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml index 87f96340..9a70f214 100644 --- a/.github/workflows/gosec.yml +++ b/.github/workflows/gosec.yml @@ -77,4 +77,8 @@ jobs: - name: Run Gosec (websocket) working-directory: ./websocket run: gosec -exclude-dir=internal ./... + # ----- + - name: Run Gosec (fgprof) + working-directory: ./fgprof + run: gosec -exclude-dir=internal ./... # ----- \ No newline at end of file diff --git a/.github/workflows/release-drafter-fgprof.yml b/.github/workflows/release-drafter-fgprof.yml new file mode 100644 index 00000000..4a325da2 --- /dev/null +++ b/.github/workflows/release-drafter-fgprof.yml @@ -0,0 +1,19 @@ +name: Release Drafter Fgprof +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + - main + paths: + - 'fgprof/**' +jobs: + draft_release_casbin: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-fgprof.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-fgprof.yml b/.github/workflows/test-fgprof.yml new file mode 100644 index 00000000..e7c40df1 --- /dev/null +++ b/.github/workflows/test-fgprof.yml @@ -0,0 +1,32 @@ +name: "Test Fgprof" + +on: + push: + branches: + - master + - main + paths: + - 'fgprof/**' + pull_request: + paths: + - 'fgprof/**' + +jobs: + Tests: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: + - 1.19.x + - 1.20.x + - 1.21.x + steps: + - name: Fetch Repository + uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: '${{ matrix.go-version }}' + - name: Run Test + working-directory: ./fgprof + run: go test -v -race ./... diff --git a/README.md b/README.md index 60e4c288..7b1de8b3 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,4 @@ Repository for third party middlewares with dependencies. * [Paseto](./paseto/README.md) * [Swagger](./swagger/README.md) * [Websocket](./websocket/README.md) +* [Fgprof](./fgprof/README.md) diff --git a/fgprof/README.md b/fgprof/README.md new file mode 100644 index 00000000..74a87f1f --- /dev/null +++ b/fgprof/README.md @@ -0,0 +1,59 @@ +--- +id: fgprof +--- + +# Fgprof + +![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=fgprof*) +[![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) +![Test](https://github.com/gofiber/contrib/workflows/Tests/badge.svg) +![Security](https://github.com/gofiber/contrib/workflows/Security/badge.svg) +![Linter](https://github.com/gofiber/contrib/workflows/Linter/badge.svg) + +[fgprof](https://github.com/felixge/fgprof) support for Fiber. + +**Note: Requires Go 1.19 and above** + +## Install + +This middleware supports Fiber v2. + +Using fgprof to profiling your Fiber app. + +``` +go get -u github.com/gofiber/fiber/v2 +go get -u github.com/gofiber/contrib/fgprof +``` + +## Config + +| Property | Type | Description | Default | +|----------|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| Next | `func(c *fiber.Ctx) bool` | A function to skip this middleware when returned `true`. | `nil` | +| Prefix | `string`. | Prefix defines a URL prefix added before "/debug/fgprof". Note that it should start with (but not end with) a slash. Example: "/federated-fiber" | `""` | + +## Example + +```go +package main + +import ( + "log" + + "github.com/gofiber/contrib/fgprof" + "github.com/gofiber/fiber/v2" +) + +func main() { + app := fiber.New() + app.Use(fgprof.New()) + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("OK") + }) + log.Fatal(app.Listen(":3000")) +} +``` + +```bash +go tool pprof -http=:8080 http://localhost:3000/debug/fgprof +``` diff --git a/fgprof/config.go b/fgprof/config.go new file mode 100644 index 00000000..f78c87d4 --- /dev/null +++ b/fgprof/config.go @@ -0,0 +1,38 @@ +package fgprof + +import "github.com/gofiber/fiber/v2" + +type Config struct { + // Next defines a function to skip this middleware when returned true. + // + // Optional. Default: nil + Next func(c *fiber.Ctx) bool + + // Prefix is the path where the fprof endpoints will be mounted. + // Default Path is "/debug/fgprof" + // + // Optional. Default: "" + Prefix string +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + Next: nil, +} + +func configDefault(config ...Config) Config { + // Return default config if nothing provided + if len(config) < 1 { + return ConfigDefault + } + + // Override default config + cfg := config[0] + + // Set default values + if cfg.Next == nil { + cfg.Next = ConfigDefault.Next + } + + return cfg +} diff --git a/fgprof/fgprof.go b/fgprof/fgprof.go new file mode 100644 index 00000000..fd149a03 --- /dev/null +++ b/fgprof/fgprof.go @@ -0,0 +1,29 @@ +package fgprof + +import ( + "github.com/felixge/fgprof" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" +) + +func New(conf ...Config) fiber.Handler { + // Set default config + cfg := configDefault(conf...) + + fgProfPath := cfg.Prefix + "/debug/fgprof" + + var fgprofHandler = adaptor.HTTPHandler(fgprof.Handler()) + + // Return new handler + return func(c *fiber.Ctx) error { + // Don't execute middleware if Next returns true + if cfg.Next != nil && cfg.Next(c) { + return c.Next() + } + + if c.Path() == fgProfPath { + return fgprofHandler(c) + } + return c.Next() + } +} diff --git a/fgprof/fgprof_test.go b/fgprof/fgprof_test.go new file mode 100644 index 00000000..b93b8204 --- /dev/null +++ b/fgprof/fgprof_test.go @@ -0,0 +1,109 @@ +package fgprof + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/utils" + "io" + "net/http/httptest" + "testing" +) + +// go test -run Test_Non_Fgprof_Path +func Test_Non_Fgprof_Path(t *testing.T) { + app := fiber.New() + app.Use(New()) + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("escaped") + }) + + resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "escaped", string(body)) +} + +// go test -run Test_Non_Fgprof_Path_WithPrefix +func Test_Non_Fgprof_Path_WithPrefix(t *testing.T) { + app := fiber.New() + app.Use(New(Config{ + Prefix: "/prefix", + })) + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("escaped") + }) + + resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "escaped", string(body)) +} + +// go test -run Test_Fgprof_Path +func Test_Fgprof_Path(t *testing.T) { + app := fiber.New() + app.Use(New()) + + // Default fgprof interval is 30 seconds + resp, err := app.Test(httptest.NewRequest("GET", "/debug/fgprof?seconds=1", nil), 1500) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) +} + +// go test -run Test_Fgprof_Path_WithPrefix +func Test_Fgprof_Path_WithPrefix(t *testing.T) { + app := fiber.New() + app.Use(New(Config{ + Prefix: "/test", + })) + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("escaped") + }) + + // Non fgprof prefix path + resp, err := app.Test(httptest.NewRequest("GET", "/prefix/debug/fgprof?seconds=1", nil), 1500) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 404, resp.StatusCode) + // Fgprof prefix path + resp, err = app.Test(httptest.NewRequest("GET", "/test/debug/fgprof?seconds=1", nil), 1500) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, resp.StatusCode) +} + +// go test -run Test_Fgprof_Next +func Test_Fgprof_Next(t *testing.T) { + app := fiber.New() + + app.Use(New(Config{ + Next: func(_ *fiber.Ctx) bool { + return true + }, + })) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/debug/pprof/", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 404, resp.StatusCode) +} + +// go test -run Test_Fgprof_Next_WithPrefix +func Test_Fgprof_Next_WithPrefix(t *testing.T) { + app := fiber.New() + + app.Use(New(Config{ + Next: func(_ *fiber.Ctx) bool { + return true + }, + Prefix: "/federated-fiber", + })) + + resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/federated-fiber/debug/pprof/", nil)) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 404, resp.StatusCode) +} diff --git a/fgprof/go.mod b/fgprof/go.mod new file mode 100644 index 00000000..c6d370c3 --- /dev/null +++ b/fgprof/go.mod @@ -0,0 +1,23 @@ +module github.com/gofiber/contrib/fgprof + +go 1.19 + +require ( + github.com/felixge/fgprof v0.9.3 + github.com/gofiber/fiber/v2 v2.51.0 +) + +require ( + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.15.0 // indirect +) diff --git a/fgprof/go.sum b/fgprof/go.sum new file mode 100644 index 00000000..8b4225d8 --- /dev/null +++ b/fgprof/go.sum @@ -0,0 +1,52 @@ +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +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= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +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/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= +github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 h1:PxlBVtIFHR/mtWk2i0gTEdCz+jBnqiuHNSki0epDbVs= +github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +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/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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=