比较常见pprof 可以把调用栈可视化成调用图,embedded-struct-visualizer 可以把Go 的项目的代码分层结构和依赖都可视化成流程图。

安装 embedded-struct-visualizer

 go install github.com/davidschlachter/embedded-struct-visualizer@latest

查看命令选项参数

embedded-struct-visualizer -h
Usage: [OPTIONS] DirToScan
If the directory to scan is not provided, it defaults to './'
OPTIONS:
  -out <file>  path to output file (default: write to stdout)
  -v           verbose logging

Example

以官方给的例子 main.go

package main

import (
	"time"
)

type A struct {
	B
	C map[string]D
}

type B struct {
	E, F  string
	G     string
	Timer H
}

type D struct {
	I uint64
}

type H struct {
	Timer time.Ticker
	J     chan D
}

生成结构关系:

> embedded-struct-visualizer
digraph {
"main.A" -> { "main.B" "main.D" };
"main.B" -> { "main.H" };
"main.H" -> { "time.Ticker" "main.D" };
}

生成结构关系 并 输出到.gv 文件:

>embedded-struct-visualizer -out .\example.gv
> ls

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2022/11/27     10:54            112 example.gv
-a----        2022/11/26     22:06             31 go.mod
-a----        2022/11/26     22:07            208 main.go

项目目录下多出了example.gv ,具体内容如下

cat .\example.gv
digraph {
"main.A" -> { "main.B" "main.D" };
"main.B" -> { "main.H" };
"main.H" -> { "time.Ticker" "main.D" };

安装 graphviz 

graphviz 是一种将结构信息表示为抽象图和网络的图的工具。

接着,把生成的gv文件利用graphviz绘制的更美观。

首先下载安装:

https://graphviz.org/download/ 查看是否安装成功(Win):

 dot -v
dot - graphviz version 7.0.2 (20221119.0110)
... <为了不篇幅过长,省略一些细节>

利用graphviz绘图

dot -Tformat[:renderer[:formatter]] -o 将输出语言设置为支持的格式之一。默认情况下,生成带属性的点。

接着把上一步生成的gv文件,生成PNG输出

dot -Tpng example.gv -o example.png

在项目目录下多出example.png

接着,实用于一个相对复杂的Gin Engine的gv

  1. 源码链接: https://github.com/gin-gonic/gin/blob/master/gin.go

  2. 生成的gv:

embedded-struct-visualizer -out .\gin.gv

gin.gv 详细如下:

digraph {"gin.mockWriter" -> { "http.Header" };"binding.QueryTest" -> { "binding.appkey" };"binding.FooBarStruct" -> { "binding.FooStruct" };"binding.FooBarFileStruct" -> { "binding.FooBarStruct" 
"multipart.FileHeader" };"binding.FooBarFileFailStruct" -> { "binding.FooBarStruct" 
"multipart.FileHeader" };"binding.FooDefaultBarStruct" -> { "binding.FooStruct" };"binding.FooStructUseNumber" -> { "binding.any" };"binding.FooStructDisallowUnknownFields" -> { "binding.any" 
};"binding.FooBarStructForTimeType" -> { "time.Time" };"binding.FooStructForTimeTypeNotUnixFormat" -> { "time.Time" 
};"binding.FooStructForTimeTypeNotFormat" -> { "time.Time" };"binding.FooStructForTimeTypeFailFormat" -> { "time.Time" };"binding.FooStructForTimeTypeFailLocation" -> { "time.Time" 
};"binding.FooStructForMapType" -> { "binding.any" };"binding.FooStructForIgnoreFormTag" -> { "binding.string" };"binding.defaultValidator" -> { "sync.Once" 
"validator.Validate" };"binding.structFull" -> { "binding." "time.Time" 
"binding.string" };"binding.S" -> { "binding.S" };"binding.testFile" -> { "binding.byte" };"binding.structNoValidationValues" -> { 
"binding.substructNoValidation" "binding.int16" 
"binding.uint16" "time.Time" "binding.mapNoValidationSub" 
"binding." };"binding.structNoValidationPointer" -> { 
"binding.substructNoValidation" "binding.uint32" 
"binding.mapNoValidationSub" "binding.int8" "binding.int32" 
"binding.uint8" "binding.uint16" "binding.float64" 
"binding.map" "binding.int" "binding.int16" "binding.int64" 
"binding.float32" "time.Time" "binding.testInterface" 
"binding.uint" "binding.uint64" "binding.string" };"gin.Context" -> { "url.Values" "gin.responseWriter" 
"gin.HandlersChain" "gin.any" "gin.errorMsgs" 
"gin.skippedNode" "sync.RWMutex" "gin.string" 
"http.SameSite" "http.Request" "gin.ResponseWriter" 
"gin.Params" "gin.Engine" };"gin.interceptedWriter" -> { "gin.ResponseWriter" 
"bytes.Buffer" };"gin.Error" -> { "gin.error" "gin.ErrorType" "gin.any" };"gin.onlyFilesFS" -> { "http.FileSystem" };"gin.neuteredReaddirFile" -> { "http.File" };"gin.RouteInfo" -> { "gin.HandlerFunc" };"gin.Engine" -> { "gin.RouterGroup" "gin.string" 
"render.HTMLRender" "gin.HandlersChain" "sync.Pool" 
"gin.methodTrees" "render.Delims" "template.FuncMap" 
"gin.uint16" "net.IPNet" };"gin.LoggerConfig" -> { "gin.LogFormatter" "io.Writer" 
"gin.string" };"gin.LogFormatterParams" -> { "http.Request" "time.Time" 
"time.Duration" "gin.any" };"render.Data" -> { "render.byte" };"render.HTMLProduction" -> { "template.Template" 
"render.Delims" };"render.HTMLDebug" -> { "render.string" "render.Delims" 
"template.FuncMap" };"render.HTML" -> { "template.Template" "render.any" };"render.JSON" -> { "render.any" };"render.IndentedJSON" -> { "render.any" };"render.SecureJSON" -> { "render.any" };"render.JsonpJSON" -> { "render.any" };"render.AsciiJSON" -> { "render.any" };"render.PureJSON" -> { "render.any" };"render.MsgPack" -> { "render.any" };"render.ProtoBuf" -> { "render.any" };"render.Reader" -> { "io.Reader" };"render.Redirect" -> { "http.Request" };"render.String" -> { "render.any" };"render.TOML" -> { "render.any" };"render.XML" -> { "render.any" };"render.YAML" -> { "render.any" };"gin.responseWriter" -> { "http.ResponseWriter" };"gin.RouterGroup" -> { "gin.HandlersChain" "gin.Engine" };"protoexample.Test" -> { "protoexample.int32" 
"protoexample.int64" "protoexample.TestOptionalGroup" 
"protoimpl.MessageState" "protoimpl.SizeCache" 
"protoimpl.UnknownFields" "protoexample.string" };"protoexample.Test_OptionalGroup" -> { 
"protoimpl.MessageState" "protoimpl.SizeCache" 
"protoimpl.UnknownFields" "protoexample.string" };"gin.methodTree" -> { "gin.node" };"gin.node" -> { "gin.node" "gin.HandlersChain" 
"gin.nodeType" };"gin.nodeValue" -> { "gin.HandlersChain" "gin.Params" };"gin.skippedNode" -> { "gin.node" "gin.int16" };}


  1. 绘制png
 dot -Tpng gin.gv -o gin.png

<ps: 图片过大可下载预览>

参考