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=