Skip to content

Commit

Permalink
feat: add pprof middleware for Hertz framework (#1)
Browse files Browse the repository at this point in the history
* init

* add adaptor

* add liscense

* add license

* unify dir name

* unify dir name
* test case
* add gotils
* readme_cn
* delete license fiber

* add group test

* license-gotils

* Update README_CN.md

* Update README_CN.md
  • Loading branch information
jc666 committed Sep 21, 2022
1 parent b261e61 commit 2b6aea2
Show file tree
Hide file tree
Showing 14 changed files with 1,127 additions and 1 deletion.
5 changes: 5 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CloudWeGo
Copyright 2022 CloudWeGo authors.

This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
118 changes: 117 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,117 @@
# .github
# pprof (This is a community driven project)

English | [中文](README_CN.md)

pprof middleware for Hertz framework, inspired by [pprof](https://github.com/gin-contrib/pprof).
This project would not have been possible without the support from the CloudWeGo community and previous work done by the gin community.

- Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool.


## Install
```shell
go get github.com/hertz-contrib/pprof
```

## Usage
### Example

```go
func main() {
h := server.Default()

pprof.Register(h)

h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})

h.Spin()
}
```

### change default path prefix

```go
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
"hertz-contrib-beiye/pprof"
)

func main() {
h := server.Default()

// default is "debug/pprof"
pprof.Register(h, "dev/pprof")

h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})

h.Spin()
}

```

### custom router group

```go
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"hertz-contrib-beiye/pprof"
"net/http"
)

func main() {
h := server.Default()

pprof.Register(h)

adminGroup := h.Group("/admin")
adminGroup.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})

pprof.RouteRegister(adminGroup, "pprof")

h.Spin()
}

```


### Use the pprof tool

Then use the pprof tool to look at the heap profile:

```bash
go tool pprof http://localhost:8888/debug/pprof/heap
```

Or to look at a 30-second CPU profile:

```bash
go tool pprof http://localhost:8888/debug/pprof/profile
```

Or to look at the goroutine blocking profile, after calling runtime.SetBlockProfileRate in your program:

```bash
go tool pprof http://localhost:8888/debug/pprof/block
```

Or to collect a 5-second execution trace:

```bash
wget http://localhost:8888/debug/pprof/trace?seconds=5
```


## License
This project is under the Apache License 2.0. See the LICENSE file for the full license text.
115 changes: 115 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# pprof (本项目由CloudWeGo社区贡献者协作开发并维护)

[English](README.md) | 中文

pprof 是为 Hertz 框架开发的中间件,参考了 [Gin](https://github.com/gin-gonic/gin)[pprof](https://github.com/gin-contrib/pprof) 的实现。
本项目的完成得益于 CloudWeGo 社区的工作以及 Gin 社区所做的相关前置工作。


## 安装
```shell
go get github.com/hertz-contrib/pprof
```

## 使用
### 代码实例1

```go
func main() {
h := server.Default()

pprof.Register(h)

h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})

h.Spin()
}
```

### 代码实例2: 自定义前缀

```go
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
"hertz-contrib-beiye/pprof"
)

func main() {
h := server.Default()

// default is "debug/pprof"
pprof.Register(h, "dev/pprof")

h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})

h.Spin()
}

```

### 代码实例3: 自定义路由组

```go
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"hertz-contrib-beiye/pprof"
"net/http"
)

func main() {
h := server.Default()

pprof.Register(h)

adminGroup := h.Group("/admin")
adminGroup.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})

pprof.RouteRegister(adminGroup, "pprof")

h.Spin()
}

```


### 如何使用

使用 `pprof tool` 工具查看堆栈采样信息:

```bash
go tool pprof http://localhost:8888/debug/pprof/heap
```

使用 `pprof tool` 工具查看 30s 的 CPU 采样信息:

```bash
go tool pprof http://localhost:8888/debug/pprof/profile
```

使用 `pprof tool` 工具查看 go 协程阻塞信息:

```bash
go tool pprof http://localhost:8888/debug/pprof/block
```

使用 `pprof tool` 工具查看 5s 内的执行 trace:

```bash
wget http://localhost:8888/debug/pprof/trace?seconds=5
```


## License
This project is under the Apache License 2.0. See the LICENSE file for the full license text.
92 changes: 92 additions & 0 deletions adaptor/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2022 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package adaptor

import (
"context"
"net/http"

"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/adaptor"
"github.com/cloudwego/hertz/pkg/common/hlog"
"github.com/cloudwego/hertz/pkg/protocol/consts"
"github.com/savsgio/gotils/strconv"
)

// NewHertzHTTPHandlerFunc wraps net/http handler to hertz app.HandlerFunc,
// so it can be passed to hertz server
//
// While this function may be used for easy switching from net/http to hertz,
// it has the following drawbacks comparing to using manually written hertz
// request handler:
//
// - A lot of useful functionality provided by hertz is missing
// from net/http handler.
//
// - net/http -> hertz handler conversion has some overhead,
// so the returned handler will be always slower than manually written
// hertz handler.
//
// So it is advisable using this function only for net/http -> hertz switching.
// Then manually convert net/http handlers to hertz handlers
func NewHertzHTTPHandlerFunc(h http.HandlerFunc) app.HandlerFunc {
return NewHertzHTTPHandler(h)
}

// NewHertzHTTPHandler wraps net/http handler to hertz app.HandlerFunc,
// so it can be passed to hertz server
//
// While this function may be used for easy switching from net/http to hertz,
// it has the following drawbacks comparing to using manually written hertz
// request handler:
//
// - A lot of useful functionality provided by hertz is missing
// from net/http handler.
//
// - net/http -> hertz handler conversion has some overhead,
// so the returned handler will be always slower than manually written
// hertz handler.
//
// So it is advisable using this function only for net/http -> hertz switching.
// Then manually convert net/http handlers to hertz handlers
func NewHertzHTTPHandler(h http.Handler) app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
req, err := adaptor.GetCompatRequest(c.GetRequest())
if err != nil {
hlog.CtxErrorf(ctx, "HERTZ: Get request error: %v", err)
c.String(http.StatusInternalServerError, consts.StatusMessage(http.StatusInternalServerError))
return
}
req.RequestURI = strconv.B2S(c.Request.RequestURI())
rw := adaptor.GetCompatResponseWriter(&c.Response)
c.ForEachKey(func(k string, v interface{}) {
ctx = context.WithValue(ctx, k, v)
})
h.ServeHTTP(rw, req.WithContext(ctx))
body := c.Response.Body()
// From net/http.ResponseWriter.Write:
// If the Header does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to DetectContentType.
if len(c.GetHeader(consts.HeaderContentType)) == 0 {
l := 512
if len(body) < 512 {
l = len(body)
}
c.Response.Header.Set(consts.HeaderContentType, http.DetectContentType(body[:l]))
}
}
}
Loading

0 comments on commit 2b6aea2

Please sign in to comment.