expvar

https://github.com/astaxie/gopkg



https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter13/13.3.html
包 expvar 为公共变量提供了一个标准化的接口,如服务器中的操作计数器。它以 JSON 格式通过 /debug/vars 接口以 HTTP 的方式公开这些公共变量。



设置或修改这些公共变量的操作是原子的。



除了为程序增加 HTTP handler,此包还注册以下变量:
cmdline os.Args
memstats runtime.Memstats
导入该包有时只是为注册其 HTTP handler 和上述变量。 要以这种方式使用,请将此包通过如下形式引入到程序中:
import _ “expvar”

创建一个在监听 1818端口的 HTTP 服务器。每个请求 hander() 后,在向访问者发送响应消息之前增加计数器。
package main
import (
“expvar”
“fmt”
“net/http”
)
var visits = expvar.NewInt(“visits”)
func handler(w http.ResponseWriter, r *http.Request) {
visits.Add(1)
fmt.Fprintf(w, “Hi there, I love %s!”, r.URL.Path[1:])
}
func main() {
http.HandleFunc(“/”, handler)
http.ListenAndServe(“:1818”, nil)
}
导入时,expvar 包将为 http.DefaultServeMux 上的模式 “/debug /vars” 注册一个处理函数。此处理程序返回已在 expvar 包中注册的所有指标。运行代码并访问 http://localhost:1818/debug/vars,您将看到如下所示的内容。输出被截断以增加可读性:
{
“cmdline”: [“/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build214947472/command-line-arguments/_obj/exe/main”],
“memstats”: {“Alloc”:374416,”TotalAlloc”:374416,”Sys”:3346432,”Lookups”:10,”Mallocs”:4831,”Frees”:87,”HeapAlloc”:374416,”HeapSys”:1736704,”HeapIdle”:548864,”HeapInuse”:1187840,”HeapReleased”:0,”HeapObjects”:4744,”StackInuse”:360448,”StackSys”:360448,”MSpanInuse”:21432,”MSpanSys”:32768,”MCacheInuse”:6944,”MCacheSys”:16384,”BuckHashSys”:2686,”GCSys”:137216,”OtherSys”:1060226,”NextGC”:4473924,”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,



信息真不少。这是因为默认情况下该包注册了os.Args 和 runtime.Memstats 两个指标。 我想在这个 JSON 响应结束时关注访问计数器。 因为计数器还没有增加,它的值仍然为0。现在通过访问http:// localhost:1818/golang 来增加计数器,然后返回。计数器不再为0。



src/expvar/expvar.go +362
func init() {
http.HandleFunc(“/debug/vars”, expvarHandler)
Publish(“cmdline”, Func(cmdline))
Publish(“memstats”, Func(memstats))
}



expvar.Publish
expvar 包相当小且容易理解。它主要由两个部分组成。第一个是函数 expvar.Publish(name string,v expvar.Var)。该函数可用于在未导出的全局注册表中注册具有特定名称的 v。以下代码段显示了具体实现。接下来的 3 个代码段是从 expvar 包的源代码中截取的。



// Publish declares a named exported variable. This should be called from a
// package’s init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
mutex.Lock()
defer mutex.Unlock()
if _, existing := vars[name]; existing {
log.Panicln(“Reuse of exported var name:”, name)
}
vars[name] = v
// 一方面,该包中所有公共变量,放在 vars 中,同时,通过 varKeys 保存了所有变量名,并且按字母序排序,即实现了一个有序的、线程安全的哈希表
varKeys = append(varKeys, name)
sort.Strings(varKeys)
}



expvar.Var
另一个重要的组件是 expvar.Var 接口。 这个接口只有一个方法:
// Var is an abstract type for all exported variables.
type Var interface {
// String returns a valid JSON value for the variable.
// Types with String methods that do not return valid JSON
// (such as time.Time) must not be used as a Var.
String() string
}



所以你可以在有 String() string 方法的所有类型上调用 Publish() 函数。



expvar.Int
expvar 包附带了其他几个类型,它们实现了 expvar.Var 接口。其中一个是 expvar.Int,我们已经在演示代码中通过 expvar.NewInt(“visits”) 方式使用它了,它会创建一个新的 expvar.Int,并使用 expvar.Publish 注册它,然后返回一个指向新创建的 expvar.Int 的指针。



var visits = expvar.NewInt(“visits”)
也是通过publish增加变量的
func NewInt(name string) *Int {
v := new(Int)
Publish(name, v)
return v
}



expvar.Int 包装一个 int64,并有两个函数 Add(delta int64) 和 Set(value int64),它们以线程安全的方式修改整数。



Other types
除了expvar.Int,该包还提供了一些实现 expvar.Var 接口的其他类型:




  • expvar.Float

  • expvar.String

  • expvar.Map

  • expvar.Func



前两个类型包装了 float64 和 string。后两种类型需要稍微解释下。



expvar.Map 类型可用于使指标出现在某些名称空间下。我经常这样用:
var stats = expvar.NewMap(“tcp”)
var requests, requestsFailed expvar.Int
func init() {
stats.Set(“requests”, &requests)
stats.Set(“requests_failed”, &requestsFailed)
}
这段代码使用名称空间 tcp 注册了两个指标 requests 和 requests_failed。它将显示在 JSON 响应中,如下所示:
{
“tcp”: {
“request”: 18,
“requests_failed”: 21
}
}



当要使用某个函数的执行结果时,您可以使用 expvar.Func。假设您希望计算应用程序的正常运行时间,每次有人访问 http://localhost:1818/debug/vars 时,都必须重新计算此值。
var start = time.Now()
func calculateUptime() interface {
return time.Since(start).String()

expvar.Publish(“uptime”, expvar.Func(calculateUptime))



关于 Handler 函数
本文开始时提到,可以简单的导入 expvar 包,然后使用其副作用,导出 /debug/vars 模式。然而,如果我们使用了一些框架,并非使用 http.DefaultServeMux,而是框架自己定义的 Mux,这时直接导入使用副作用可能会不生效。这时我们可以按照使用的框架,定义自己的路径,比如也叫:/debug/vars,然后,这对应的处理程序中,有两种处理方式:



1)将处理直接交给 expvar.Handler,比如:
handler := expvar.Handler()
handler.ServeHTTP(w, req)
2)自己遍历 expvar 中的公共变量,构造输出,甚至可以过滤 expvar 默认提供的 cmdline 和 memstats,我们看下 expvarHandler 的源码就明白了:(通过 expvar.Do 函数来遍历)
func expvarHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set(“Content-Type”, “application/json; charset=utf-8”)
fmt.Fprintf(w, “{\n”)
first := true
Do(func(kv KeyValue) {
if !first {
fmt.Fprintf(w, “,\n”)
}
first = false
fmt.Fprintf(w, “%q: %s”, kv.Key, kv.Value)
})
fmt.Fprintf(w, “\n}\n”)
}



expvar包提供了公共变量的标准接口,如服务的操作计数器。本包通过HTTP在/debug/vars位置以JSON格式导出了这些变量。+
对这些公共变量的读写操作都是原子级的。
为了增加HTTP处理器,本包注册了如下变量:+



cmdline os.Args
memstats runtime.Memstats
1
2
有时候本包被导入只是为了获得本包注册HTTP处理器和上述变量的副作用。此时可以如下方式导入本包:



import _ “expvar”
1
支持类型
支持一些常见的类型:float64、int64、Map、String。
如果我们的程序要用到上面提的四种类型(其中,Map 类型要求 Key 是字符串)。可以考虑使用这个包。



功能
支持对变量的基本操作,修改、查询



整形类型,可以用来做计数器



线程安全的



此外还提供了调试接口,/debug/vars。它能够展示所有通过这个包创建的变量



所有的变量都是Var类型,可以自己通过实现这个接口扩展其它的类型



Handler()方法可以得到调试接口的http.Handler,和自己的路由对接



func Do



func Do(f func(KeyValue))
1
Do calls f for each exported variable. The global variable map is locked during the iteration, but existing entries may be concurrently updated.
Do对映射的每一条记录都调用f。迭代执行时会锁定该映射,但已存在的记录可以同时更新。



func Handler



func Handler() http.Handler
1
Handler returns the expvar HTTP Handler.



This is only needed to install the handler in a non-standard location.



func Publish



func Publish(name string, v Var)
1
Publish declares a named exported variable. This should be called from a package’s init function when it creates its Vars. If the name is already registered then this will log.Panic.
Publish声明一个导出变量。必须在init函数里调用。如果name已经被注册,会调用log.Panic。



Category golang