Go语言精进之路:从新手到高手的编程思想、方法和技巧(2)
上QQ阅读APP看书,第一时间看更新

48.1 expvar包的工作原理

Go标准库中的expvar包提供了一种输出应用内部状态信息的标准化方案,这个方案标准化了以下三方面内容:

  • 数据输出接口形式;
  • 输出数据的编码格式;
  • 用户自定义性能指标的方法。

Go应用通过expvar包输出内部状态信息的工作原理如图48-1所示。

015-01

图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所示的结果。

015-01

图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
}