Skip to content

Commit

Permalink
Merge pull request #880 from Max-Cheng/main
Browse files Browse the repository at this point in the history
🚀 Add fgprof profiler
  • Loading branch information
ReneWerner87 committed Dec 6, 2023
2 parents 2b1494f + 8028596 commit 10ad0d2
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,10 @@ updates:
- "🤖 Dependencies"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/fgprof" # Location of package manifests
labels:
- "🤖 Dependencies"
schedule:
interval: "daily"

50 changes: 50 additions & 0 deletions .github/release-drafter-fgprof.yml
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions .github/workflows/gosec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
# -----
19 changes: 19 additions & 0 deletions .github/workflows/release-drafter-fgprof.yml
Original file line number Diff line number Diff line change
@@ -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 }}
32 changes: 32 additions & 0 deletions .github/workflows/test-fgprof.yml
Original file line number Diff line number Diff line change
@@ -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 ./...
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ Repository for third party middlewares with dependencies.
* [Paseto](./paseto/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+paseto%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Swagger](./swagger/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swagger%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swagger.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Websocket](./websocket/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+websocket%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-websocket.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Fgprof](./fgprof/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Fgprof%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-fgprof.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
59 changes: 59 additions & 0 deletions fgprof/README.md
Original file line number Diff line number Diff line change
@@ -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
```
38 changes: 38 additions & 0 deletions fgprof/config.go
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions fgprof/fgprof.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
109 changes: 109 additions & 0 deletions fgprof/fgprof_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
23 changes: 23 additions & 0 deletions fgprof/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
Loading

0 comments on commit 10ad0d2

Please sign in to comment.