简介
本文主要基于expvar和pprof做性能对比。expvar 提供了 Var 接口,允许开发者定义自己的性能指标,这些指标会自动暴露给外部工具。Go 标准库已包含 expvar 的初始化,自动注册 HTTP 手柄。
1 expvar 输出度量数据。辅助定位性能瓶颈
对不同类型的性能数据进行收集和采样,go提供了内置的模块和方法。
比如pprof 和 expvar,在微观层面,采样通过运行性能基准测试收集和采样数据的方法,这种方法适用于定位函数或方法实现中存在性能瓶颈点的情形。
在宏观层面,采用独立程序进行性能数据采样,往往很难快速捕捉到真正的瓶颈,尤其对那些内部结构复杂,业务逻辑过多,内部较多并发的程序。
我们在对这样的程序进行性能采样时,真正的瓶颈可能被其他数据覆盖。
那么如何能更高效捕捉到应用的性能瓶颈呢,我们需要知道go程序运行的状态。
应用运行一般状态以度量数据的形式呈现。通过了解应用关键路径的度量数据,我们可以确定某个度量点的应用性能是否符合预期性能指标。
这样可以最大程度缩小性能瓶颈搜索范围。 从而快速定位应用的瓶颈点进行优化。
这些可以反映应用运行状态的数据也被成为应用的内省 introspection 数据。
相比通过查询外部特征获取的 探针类 probing 数据(比如查看应用某个端口是否有响应并返回正确的数据或状态码),内省数据可以传达更丰富,更多有关应用程序状态的上下文信息。
这些上下文信息可以是应用对各类资源的占用信息,比如应用运行了多少内存空间,可以是自定义的性能指标信息,比如单位时间处理外部请求数量,应答延迟,队列积压量,等等。
在c++ java中,并没有内置输出应用状态度量数据的设施 ,接口方法,指标定义方法,数据输出格式等等,需要开发者自己通过编码实现或利用第三方库实现。
go自带电池的编码语言,我们可以轻松使用go标准库提供的 expvar 包按统一接口,统一数据格式,一致的指标定义方法输出自定义度量数据。 我们一起看看如何使用 expvar 输出自定义性能度量数据。
2 expvar 工作原理
go标准库的expvat 包提供了一个输出应用程序状态信息的标准化方案,它包括以下3个内容:
1 输出数据的接口形式
2 输出数据的编码格式
3 用户自定义性能指标方法
go 应用通过expvar 包输出内部状态信息的工作原理如下
------------------------
| go app | http get cmd
| import _ "expvar" | ---(/debug/vars) ---> http get web
| var ( ... ) | cmdline: ["xx"]
| | memstats: {...}
| | msg_in:6
------------------------ msg_out:5
custom_var:xx
expvar 标准库
type Var interface {
String() string
}
func init() {
http.HandleFunc("/debug/vars", expvarHandler)
}
从上图知道,go应用如果需要输出自身状态数据,需要如下形式导出
import _ expvar
与net/http/pprof 类似,expvar 包也在自己的initial函数向http包的默认请求路由器 defaultServeMux注册一个服务端点 /debug/vars
//$GOROOT/src/expvar/expvar.go
func init() {
http.HandleFunc("/debug/vars", expvarHandler)
...
}
这个服务端点就是 expvar 提供给外部的获取应用内部状态的唯一标准接口,
外部工具(无论命令行还是基于web的图形化程序)都可以通过标准的http get 请求从该服务器端点获取内部状态数据,比如
//expvar_demo1.go
package main
import (
"expvar"
_ "expvar"
"fmt"
"io"
"net/http"
)
var (
msgln * expvar.Int
msgOut * expvar.Float
customeVar * CustomVar
)
type CustomVar struct{}
// type Var interface {
// String() string
// }
func (cv * CustomVar) String() string {
return "ok"
}
func init() {
msgln = expvar.NewInt("msg_in")
msgOut = expvar.NewFloat("msg_out")
customeVar = &CustomVar{}
expvar.Publish("custom_var", customeVar)
}
func HomeHandler(w http.ResponseWriter, r * http.Request) {
// w.Write([]byte("welcome.!"))
io.WriteString(w, "welcome.!")
}
func main() {
serInfo := "localhost:8801"
http.HandleFunc("/home", HomeHandler)
fmt.Printf("serInfo:%v \n", serInfo)
fmt.Println(http.ListenAndServe(serInfo, nil))
}
启动该服务后,访问首页,/home 返回 字符welcome,访问debug 信息 http://127.0.0.1:8809/debug/vars
{
"cmdline": ["4031153171\\b001\\exe\\main.exe"],
"custom_var": ok,
"memstats": {"Alloc":256984,"TotalAlloc":256984,"Sys":11190480,"Lookups":0,"Mallocs":1438,"Frees":95,"HeapAlloc":256984,"HeapSys":4030464,"HeapIdle":2818048,"HeapInuse":1212416,"HeapReleased":2818048,"HeapObjects":1343,"StackInuse":163840,"StackSys":163840,"MSpanInuse":29376,"MSpanSys":32640,"MCacheInuse":9344,"MCacheSys":16352,"BuckHashSys":6277,"GCSys":6025192,"OtherSys":915715,"NextGC":4194304,"LastGC":0,"PauseTotalNs":0,"PauseNs":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":0,"NumForcedGC":0,"GCCPUFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":21,"Frees":0},{"Size":16,"Mallocs":486,"Frees":0},{"Size":24,"Mallocs":110,"Frees":0},{"Size":32,"Mallocs":93,"Frees":0},{"Size":48,"Mallocs":329,"Frees":0},{"Size":64,"Mallocs":40,"Frees":0},{"Size":80,"Mallocs":28,"Frees":0},{"Size":96,"Mallocs":20,"Frees":0},{"Size":112,"Mallocs":10,"Frees":0},{"Size":128,"Mallocs":8,"Frees":0},{"Size":144,"Mallocs":6,"Frees":0},{"Size":160,"Mallocs":21,"Frees":0},{"Size":176,"Mallocs":7,"Frees":0},{"Size":192,"Mallocs":0,"Frees":0},{"Size":208,"Mallocs":32,"Frees":0},{"Size":224,"Mallocs":5,"Frees":0},{"Size":240,"Mallocs":6,"Frees":0},{"Size":256,"Mallocs":12,"Frees":0},{"Size":288,"Mallocs":7,"Frees":0},{"Size":320,"Mallocs":4,"Frees":0},{"Size":352,"Mallocs":10,"Frees":0},{"Size":384,"Mallocs":1,"Frees":0},{"Size":416,"Mallocs":19,"Frees":0},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":0,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":3,"Frees":0},{"Size":640,"Mallocs":10,"Frees":0},{"Size":704,"Mallocs":4,"Frees":0},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":1,"Frees":0},{"Size":1024,"Mallocs":14,"Frees":0},{"Size":1152,"Mallocs":4,"Frees":0},{"Size":1280,"Mallocs":3,"Frees":0},{"Size":1408,"Mallocs":4,"Frees":0},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1792,"Mallocs":3,"Frees":0},{"Size":2048,"Mallocs":2,"Frees":0},{"Size":2304,"Mallocs":2,"Frees":0},{"Size":2688,"Mallocs":2,"Frees":0},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3200,"Mallocs":0,"Frees":0},{"Size":3456,"Mallocs":0,"Frees":0},{"Size":4096,"Mallocs":4,"Frees":0},{"Size":4864,"Mallocs":0,"Frees":0},{"Size":5376,"Mallocs":1,"Frees":0},{"Size":6144,"Mallocs":1,"Frees":0},{"Size":6528,"Mallocs":0,"Frees":0},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":1,"Frees":0},{"Size":9472,"Mallocs":8,"Frees":0},{"Size":9728,"Mallocs":0,"Frees":0},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":1,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":18432,"Mallocs":0,"Frees":0}]},
"msg_in": 0,
"msg_out": 0
}
如果应用程序本身并没有使用默认 路由器 DefaultServeMux 那么就需要人工将 expvar的服务端点注册到应用程序的路由器。
expvar 包提供了Handler 函数,可以用于内部expvarHandler注册
//expvar_demo2.go
package main
返回的数据如下,为JSON格式,包括cmdline,memstats两个类别的数据。
{
"cmdline": ["60480842\\b001\\exe\\main.exe"],
"memstats": {"Alloc":1782760,"TotalAlloc":3203056,"Sys":16372128,"Lookups":0,"Mallocs":26440,"Frees":15554,"HeapAlloc":1782760,"HeapSys":8028160,"HeapIdle":4046848,"HeapInuse":3981312,"HeapReleased":2932736,"HeapObjects":10886,"StackInuse":360448,"StackSys":360448,"MSpanInuse":99144,"MSpanSys":114240,"MCacheInuse":9344,"MCacheSys":16352,"BuckHashSys":6974,"GCSys":6738048,"OtherSys":1107906,"NextGC":4194304,"LastGC":1674791690593938100,"PauseTotalNs":18400,"PauseNs":[18400,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1674791690593938100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":1,"NumForcedGC":0,"GCCPUFraction":0.012216102957431063,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":530,"Frees":243},{"Size":16,"Mallocs":8649,"Frees":4292},{"Size":24,"Mallocs":814,"Frees":164},{"Size":32,"Mallocs":2580,"Frees":1517},{"Size":48,"Mallocs":2430,"Frees":702},{"Size":64,"Mallocs":949,"Frees":389},{"Size":80,"Mallocs":390,"Frees":292},{"Size":96,"Mallocs":143,"Frees":19},{"Size":112,"Mallocs":4344,"Frees":3677},{"Size":128,"Mallocs":351,"Frees":316},{"Size":144,"Mallocs":52,"Frees":17},{"Size":160,"Mallocs":577,"Frees":268},{"Size":176,"Mallocs":19,"Frees":8},{"Size":192,"Mallocs":71,"Frees":53},{"Size":208,"Mallocs":130,"Frees":48},{"Size":224,"Mallocs":72,"Frees":55},{"Size":240,"Mallocs":31,"Frees":21},{"Size":256,"Mallocs":58,"Frees":21},{"Size":288,"Mallocs":89,"Frees":41},{"Size":320,"Mallocs":234,"Frees":175},{"Size":352,"Mallocs":35,"Frees":13},{"Size":384,"Mallocs":19,"Frees":6},{"Size":416,"Mallocs":76,"Frees":16},{"Size":448,"Mallocs":10,"Frees":4},{"Size":480,"Mallocs":4,"Frees":3},{"Size":512,"Mallocs":62,"Frees":11},{"Size":576,"Mallocs":87,"Frees":11},{"Size":640,"Mallocs":234,"Frees":76},{"Size":704,"Mallocs":27,"Frees":8},{"Size":768,"Mallocs":13,"Frees":1},{"Size":896,"Mallocs":25,"Frees":15},{"Size":1024,"Mallocs":75,"Frees":18},{"Size":1152,"Mallocs":32,"Frees":12},{"Size":1280,"Mallocs":67,"Frees":32},{"Size":1408,"Mallocs":4,"Frees":2},{"Size":1536,"Mallocs":9,"Frees":8},{"Size":1792,"Mallocs":27,"Frees":13},{"Size":2048,"Mallocs":17,"Frees":9},{"Size":2304,"Mallocs":3,"Frees":0},{"Size":2688,"Mallocs":44,"Frees":14},{"Size":3072,"Mallocs":7,"Frees":1},{"Size":3200,"Mallocs":2,"Frees":0},{"Size":3456,"Mallocs":9,"Frees":7},{"Size":4096,"Mallocs":29,"Frees":5},{"Size":4864,"Mallocs":6,"Frees":2},{"Size":5376,"Mallocs":20,"Frees":9},{"Size":6144,"Mallocs":8,"Frees":0},{"Size":6528,"Mallocs":1,"Frees":1},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":9,"Frees":0},{"Size":9472,"Mallocs":14,"Frees":3},{"Size":9728,"Mallocs":3,"Frees":1},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":9,"Frees":2},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":1,"Frees":0},{"Size":16384,"Mallocs":1,"Frees":0},{"Size":18432,"Mallocs":1,"Frees":0}]}
}
在默认返回的状态数据中,包含了 cmdline 和 memsatts。 这两个输出数据是 expvar 在init 函数就发布过的publish的变量。
//$GOROOT/src/expvar/expvar.go
func init() {
http.HandleFunc("/debug/vars", expvarHandler)
Publish("cmdline",Func(cmdline))
Publish("memstats", Func(memstats))
}
cmdline 字段含义是输出数据的应用名,这里因为通过go run 运行的应用,所以cmdline值是一个临时路径的应用。
而memstats输出的数据对应的是runtime.Memstats结构体,反映的是应用在运行期间堆内存分配,栈内存分配和GC状态。
runtime.Memstats结构体的字段可能会随着Go版本演进而变化,字段具体含义参考 Memstats的注释。 $GOROOT/src/expvar/expvar.go
3 输出自定义度量数据
expvar 自定义度量数据输出
expvar 包为go应用输出内部状态的标准化方案,
1, 接口化标准(默认为debug/vars接口获取数据);
2, 标准数据编码Json;
3, 自定义输出度量数据的标准方法;从debug/var中获取的JSON数据有名称为 custom_var 的字段,这是一个自定义的度量数据接口 expvar String()。
全部实现了方法 String() 的结构体,都可以被发布并作为输出的应用内部状态的一部分。
type cusToms struct { Value int64 } func (ct *cusToms) String() string { ss := strconv.FormatInt(atomic.LoadInt64(&ct.Value), 10) return ss } func (ct *cusToms) Add(delta int64) { atomic.AddInt64(&ct.Value, delta) } func (ct *cusToms) Set(value int64) { atomic.StoreInt64(&ct.Value, value) }
除了go标准中接口需要实现的 String(),这里还实现了 Add 和 Set 用于新增和修改。
通过Add对该自定义指标自增某一个量,也可以通过Set重置该指标。我们在设计能反映Go应用内部状态的自定义指标时,经常设计以下两个类型的指标:
测量型
这类指标是数字,支持上下增减。我们定期获取该指标的快照。常见的CPU,内存使用都是这个类型。
在业务层面,当前网站的在线访客数据,当前业务系统平均响应延迟都属于这类指标。
计数型
这类指标也是数字,它的特点是随着时间推移,数值不会减少。虽然它们永远不会减少,但有时可以重置为0.
它们再次开始递增,如:系统正常运行时间,某端口收发包字节数,24消失入队列的消息数量。
计数的优势在可以用于计算变化率: 将 T + 1 时刻获取的指标值与T时刻的指标值做比较,即可获得两个时刻之间的变化。
这两类常见指标,我们无需像之前的 CustomVar那样自行实现,expvar 包提供了对常用指标类型的原生支持。
1.4 实例: 度量指标:比如整型,浮点数指标
以及像memstats 那样的 Map型复合指标。 比如整型指标
type Int struct {
V int64
}
func (ct *Int) Value() int64 {
return atomic.LoadInt64(&ct.V)
}
func (ct *Int) String() string {
ss := strconv.FormatInt(atomic.LoadInt64(&ct.V), 10)
return ss
}
func (ct *Int) Add(delta int64) {
atomic.AddInt64(&ct.V, delta)
}
func (ct *Int) Set(value int64) {
atomic.StoreInt64(&ct.V, value)
}
Int 类型在满足Var接口的同时,还实现了 Add,Set Value 方法,方便我们使用它创建测量型,计数型指标,并且对其值的修改是并发安全的。
针对expvar.Int类型,expvar也提供了创建即发布的NewInt函数,这样我们无需再调用Publish函数发布指标
func NewInt(name string) *Int {
v := new(Int)
Publish(name, v)
return v
}
将 之前的 customVar 指标改为使用 expvar.Int 实现:
var customVar * expvar.Int
func init() {
cusToms = * expvar.NewInt("customVar")
cusToms.Set(time.Now().UnixMilli())
}
router.GET("/debug/vars", CtxExpHand)
//模拟一个后台任务 ,计数加1 耗时100 毫秒
go func() {
for {
cusToms.Add(1)
time.Sleep(time.Millisecond * 100)
}
}()
使用内置的类型 expvar.Int 时,代码更简洁。
复合类型的 自定义性能数据
expvar.Map 类型定义一个像memstats那样的复合指标
性能计数:使用内置的类型
var cusTomInt *expvar.Int
性能计数:复合性能数据
var custMap *expvar.Map
func init() {
性能计数
cusTomInt = expvar.NewInt("customVar")
cusTomInt.Set(1)
性能复合数据
custMap = expvar.NewMap("customMap")
var fieldOne expvar.Int
var fieldTwo expvar.Float
custMap.Set("fieldOne", &fieldOne)
custMap.Set("fieldTwo", &fieldTwo)
如果完全自己定义的 性能指标,则需要人工 Publish
// expvar.Publish("custom_var", &cusToms{time.Now().UnixMilli()})
}
在路由注册,或 在接口中 模拟后台任务性能计数
模拟一个后台任务 100 毫秒, 直到返回
go func() {
for {
cusTomInt.Add(1)
custMap.Add("fieldOne", 1)
custMap.AddFloat("fieldTwo", 0.01)
time.Sleep(time.Millisecond * 100)
}
}()
调用debug/vars 返回这两个参数的数据
"customMap": {"fieldOne": 41, "fieldTwo": 0.4100000000000002},
"customVar": 42,
如上,一个expvar.Map 类型变量定义后,可以向该复合指标变量添加指标,比较示例的 fieldOne。
在业务逻辑中,可以通过 expvar.Map 提供的 Add,AddFloat 方法对复合指标内部单个指标进行更新。
如果希望把一个结构体类型当作一个复合指标直接输出,expvar 也提供了很好的支持。
将结构体体作为复合类型直接输出
type CustomVarStruct struct {
FieldOne int64 `json:"field_one" `
FieldTwo float64 `json:"field_two"`
}
var (
field_one expvar.Int
field_two expvar.Float
)
func exportStruct() interface{} {
return CustomVarStruct{
FieldOne: field_one.Value(),
FieldTwo: field_two.Value(),
}
}
发布到接口
func init() {
将结构体直接输出为性能数据
expvar.Publish("CustomVarStruct", expvar.Func(exportStruct))
}
在路由注册或接口中
模拟一个后台任务 100 毫秒, 直到返回。
go func() {
for {
输出 整型性能数据:
cusTomInt.Add(1)
输出 复合类型 性能数据
custMap.Add("fieldOne", 1)
custMap.AddFloat("fieldTwo", 0.01)
输出结构体信息
field_one.Add(1)
field_two.Add(0.01)
time.Sleep(time.Millisecond * 100)
}
}()
调用接口,结构体信息将作为 字典返回
"CustomVarStruct": {"field_one":43,"field_two":0.4300000000000002},
4 小结
我们看到,针对结构体类型,expvar 包并未提供整型,浮点型,Map类型那样的直接支持。
而是通过实现一个 返回 interface{}的函数(这里是 exportStruct),并通过Publish 函数将该函数发布出去。
expvar.Func(exportStruct)
这个返回 interface{} 类型的函数的返回值底层类型必须是一个支持序列号的JSON格式的类型。
在上面的示例中,这个返回值底层类型为 CustomVarStruct 结构体,该类型本身支持被序列化为一个JSON 文件
通过这种结构体通用方法可以发布任何类型的自定义指标。