48.1 expvar包的工作原理
Go标准库中的expvar包提供了一种输出应用内部状态信息的标准化方案,这个方案标准化了以下三方面内容:
- 数据输出接口形式;
- 输出数据的编码格式;
- 用户自定义性能指标的方法。
Go应用通过expvar包输出内部状态信息的工作原理如图48-1所示。
图48-1 expvar包工作原理
从图48-1中我们看到,Go应用如果需要输出自身状态数据,需要以下面的形式导入expvar:
import _ "expvar"
和net/http/pprof类似,expvar包也在自己的init函数中向http包的默认请求“路由器”DefaultServeMux注册一个服务端点/debug/vars:
// $GOROOT/src/expvar/expvar.go func init() { http.HandleFunc("/debug/vars", expvarHandler) ... }
这个服务端点就是expvar提供给外部的获取应用内部状态的唯一标准接口,外部工具(无论是命令行还是基于Web的图形化程序)都可以通过标准的http get请求从该服务端点获取应用内部状态数据。下面是一个简单的例子:
// chapter8/sources/expvar_demo1.go package main import ( _ "expvar" "fmt" "net/http" ) func main() { http.Handle("/hi", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) })) fmt.Println(http.ListenAndServe("localhost:8080", nil)) }
运行上述示例后,通过浏览器访问http://localhost:8080/debug/vars将得到如图48-2所示的结果。
图48-2 通过浏览器访问expvar包注册的服务端点
如果应用程序本身并没有使用默认“路由器”DefaultServeMux,那么我们需要手动将expvar包的服务端点注册到应用程序所使用的“路由器”上。expvar包提供了Handler函数,该函数可用于其内部expvarHandler的注册。
// expvar_demo2.go package main import ( "expvar" "fmt" "net/http" ) func main() { mux := http.NewServeMux() mux.Handle("/hi", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) })) mux.Handle("/debug/vars", expvar.Handler()) fmt.Println(http.ListenAndServe("localhost:8080", mux)) }
如果应用程序本身并没有启动HTTP服务,那么还需在一个单独的goroutine中启动一个HTTP服务,这样expvar提供的服务才能有效。
从图48-2中我们还可以看到,expvar包提供的内部状态服务端点返回的是标准的JSON格式数据。样例如下:
{ "cmdline": ["/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build507091832/ b001/exe/expvar_demo2"], "memstats": { "Alloc": 223808, "TotalAlloc": 223808, "Sys": 71387144, "Lookups": 0, "Mallocs": 743, "Frees": 11, ... } }
在默认返回的状态数据中包含了两个字段:cmdline和memstats。这两个输出数据是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 func memstats() interface{} { stats := new(runtime.MemStats) runtime.ReadMemStats(stats) return *stats }