源码说明
在 expvar.go
中有如下说明:
expvar 包提供了一种标准化接口用于公共变量,例如针对 server 中的操作计数器; expvar 以 JSON 格式通过 HTTP 的
/debug/vars
来暴露这些变量; 针对这些公共变量的 set 或 modify 操作具有原子性;除了会添加 HTTP handler 以外,该包还会注册如下变量:
cmdline
os.Argsmemstats
runtime.Memstats有些时候导入该包的目的仅仅是为了“副作用”,即注册其提供的 HTTP handler 和上述变量; 可以按照如下方式导入该包:
import _ "expvar"
产生“副作用”的代码:
// Do calls f for each exported variable. // The global variable map is locked during the iteration,// but existing entries may be concurrently updated. func Do(f func(KeyValue)) { mutex.RLock() defer mutex.RUnlock() for _,k := range varKeys { f(KeyValue{k,vars[k]}) } } // 针对 /debug/vars 的回调函数 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") } // 获取命令参数 func cmdline() interface{} { return os.Args } // 获取内存统计信息 func memstats() interface{} { stats := new(runtime.MemStats) runtime.ReadMemStats(stats) return *stats } // 包 expvar 默认提供的东东 func init() { http.HandleFunc("/debug/vars",expvarHandler) Publish("cmdline",Func(cmdline)) Publish("memstats",Func(memstats)) }
一个简单的使用示例:
package main import "expvar" import "net" import "fmt" import "net/http" var ( counts = expvar.NewMap("counters") ) func init() { counts.Add("a",10) counts.Add("b",10) } func main() { sock,err := net.Listen("tcp","localhost:8123") if err != nil { panic("sock error") } go func() { fmt.Println("HTTP now available at port 8123") http.Serve(sock,nil) }() fmt.Println("hello") select {} }
启动示例 server
➜ Golang ./expvar_usage_1 hello HTTP now available at port 8123
➜ ~ curl http://localhost:8123/debug/vars { "cmdline": ["./expvar_usage_1"],"counters": {"a": 10,"b": 10},"memstats": {"Alloc":321496,"TotalAlloc":321496,"Sys":3084288,"Lookups":5,"Mallocs":4743,"Frees":98,"HeapAlloc":321496,"HeapSys":1769472,"HeapIdle":1040384,"HeapInuse":729088,"HeapReleased":0,"HeapObjects":4645,"StackInuse":327680,"StackSys":327680,"MSpanInuse":14080,"MSpanSys":16384,"MCacheInuse":4800,"MCacheSys":16384,"BuckHashSys":2357,"GCSys":131072,"OtherSys":820939,"NextGC":4194304,"LastGC":0,"PauseTotalNs":0,"PauseNs":[0,0],"PauseEnd":[0,"NumGC":0,"GCcpuFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":15,{"Size":16,"Mallocs":333,{"Size":32,"Mallocs":3926,{"Size":48,"Mallocs":101,{"Size":64,"Mallocs":30,{"Size":80,"Mallocs":28,{"Size":96,"Mallocs":8,{"Size":112,"Mallocs":4,{"Size":128,"Mallocs":7,{"Size":144,"Mallocs":3,{"Size":160,"Mallocs":16,{"Size":176,{"Size":192,{"Size":208,"Mallocs":24,{"Size":224,{"Size":240,"Mallocs":2,{"Size":256,"Mallocs":9,{"Size":288,"Mallocs":18,{"Size":320,{"Size":352,{"Size":384,{"Size":416,"Mallocs":26,{"Size":448,{"Size":480,"Mallocs":1,{"Size":512,{"Size":576,{"Size":640,{"Size":704,"Mallocs":6,{"Size":768,{"Size":896,{"Size":1024,{"Size":1152,"Mallocs":5,{"Size":1280,{"Size":1408,{"Size":1536,{"Size":1664,{"Size":2048,{"Size":2304,{"Size":2560,{"Size":2816,{"Size":3072,{"Size":3328,{"Size":4096,{"Size":4608,{"Size":5376,{"Size":6144,{"Size":6400,{"Size":6656,{"Size":6912,{"Size":8192,{"Size":8448,{"Size":8704,{"Size":9472,{"Size":10496,{"Size":12288,{"Size":13568,{"Size":14080,{"Size":16384,{"Size":16640,{"Size":17664,"Frees":0}]} } ➜ ~
一个golang http包自带的绝佳示例
这个示例完整展现了 expvar 包中可以使用的各种数据类型,示例代码如下
package main import ( "bytes" "expvar" "flag" "fmt" "io" "log" "net/http" "os" "os/exec" "strconv" "sync" ) // hello world,the web server var helloRequests = expvar.NewInt("hello-requests") func HelloServer(w http.ResponseWriter,req *http.Request) { helloRequests.Add(1) io.WriteString(w,"hello,world!\n") } // Simple counter server. POSTing to it will set the value. type Counter struct { mu sync.Mutex // protects n n int } // This makes Counter satisfy the expvar.Var interface,so we can export // it directly. func (ctr *Counter) String() string { ctr.mu.Lock() defer ctr.mu.Unlock() return fmt.Sprintf("%d",ctr.n) } func (ctr *Counter) ServeHTTP(w http.ResponseWriter,req *http.Request) { ctr.mu.Lock() defer ctr.mu.Unlock() switch req.Method { case "GET": ctr.n++ case "POST": buf := new(bytes.Buffer) io.Copy(buf,req.Body) body := buf.String() if n,err := strconv.Atoi(body); err != nil { fmt.Fprintf(w,"bad POST: %v\nbody: [%v]\n",err,body) } else { ctr.n = n fmt.Fprint(w,"counter reset\n") } } fmt.Fprintf(w,"counter = %d\n",ctr.n) } // simple flag server var booleanflag = flag.Bool("boolean",true,"another flag for testing") func FlagServer(w http.ResponseWriter,req *http.Request) { w.Header().Set("Content-Type","text/plain; charset=utf-8") fmt.Fprint(w,"Flags:\n") flag.VisitAll(func(f *flag.Flag) { if f.Value.String() != f.DefValue { fmt.Fprintf(w,"%s = %s [default = %s]\n",f.Name,f.Value.String(),f.DefValue) } else { fmt.Fprintf(w,"%s = %s\n",f.Value.String()) } }) } // simple argument server func ArgServer(w http.ResponseWriter,req *http.Request) { for _,s := range os.Args { fmt.Fprint(w,s," ") } } // a channel (just for the fun of it) type Chan chan int func ChanCreate() Chan { c := make(Chan) go func(c Chan) { for x := 0; ; x++ { c <- x } }(c) return c } func (ch Chan) ServeHTTP(w http.ResponseWriter,req *http.Request) { io.WriteString(w,fmt.Sprintf("channel send #%d\n",<-ch)) } // exec a program,redirecting output func DateServer(rw http.ResponseWriter,req *http.Request) { rw.Header().Set("Content-Type","text/plain; charset=utf-8") date,err := exec.Command("/bin/date").Output() if err != nil { http.Error(rw,err.Error(),500) return } rw.Write(date) } func Logger(w http.ResponseWriter,req *http.Request) { log.Print(req.URL) http.Error(w,"oops",404) } var webroot = flag.String("root",os.Getenv("HOME"),"web root directory") func main() { flag.Parse() // The counter is published as a variable directly. ctr := new(Counter) expvar.Publish("counter",ctr) http.Handle("/counter",ctr) http.Handle("/",http.HandlerFunc(Logger)) http.Handle("/go/",http.StripPrefix("/go/",http.FileServer(http.Dir(*webroot)))) http.Handle("/chan",ChanCreate()) http.HandleFunc("/flags",FlagServer) http.HandleFunc("/args",ArgServer) http.HandleFunc("/go/hello",HelloServer) http.HandleFunc("/date",DateServer) err := http.ListenAndServe(":12345",nil) if err != nil { log.Panicln("ListenAndServe:",err) } }
测试
➜ ~ curl http://localhost:12345/counter counter = 1 ➜ ~ curl http://localhost:12345/counter counter = 2 ➜ ~ curl http://localhost:12345/counter counter = 3 ➜ ~ ➜ ~ curl -XPOST http://localhost:12345/counter -d '100' counter reset counter = 100 ➜ ~ curl http://localhost:12345/counter counter = 101 ➜ ~ curl http://localhost:12345/counter counter = 102 ➜ ~ curl -XPOST http://localhost:12345/counter -d '100a' bad POST: strconv.ParseInt: parsing "100a": invalid Syntax body: [100a] counter = 102 ➜ ~ ➜ ~ curl http://localhost:12345/counter counter = 103 ➜ ~ ➜ ~ curl http://localhost:12345/ oops ➜ ~ ➜ ~ curl http://localhost:12345/go <a href="/go/">Moved Permanently</a>. ➜ ~ ➜ ~ curl -L http://localhost:12345/go <pre> <a href=".bash_history">.bash_history</a> <a href=".bash_profile">.bash_profile</a> <a href=".bash_sessions/">.bash_sessions/</a> <a href="http_-L.pcap">http_-L.pcap</a> <a href="http_-d.pcap">http_-d.pcap</a> <a href="http_-e.pcap">http_-e.pcap</a> <a href="http_-r.pcap">http_-r.pcap</a> <a href="workspace/">workspace/</a> </pre> ➜ ~ ➜ ~ curl http://localhost:12345/chan channel send #0 ➜ ~ curl http://localhost:12345/chan channel send #1 ➜ ~ curl http://localhost:12345/chan channel send #2 ➜ ~ ➜ ~ curl http://localhost:12345/flags Flags: boolean = true root = /Users/sunfei ➜ ~ ➜ ~ curl http://localhost:12345/args ./expvar_usage_2 % ➜ ~ ➜ ~ curl http://localhost:12345/go/hello hello,world! ➜ ~ ➜ ~ curl http://localhost:12345/date 2017年 1月12日 星期四 17时16分46秒 CST ➜ ~ curl http://localhost:12345/date 2017年 1月12日 星期四 17时16分51秒 CST ➜ ~ curl http://localhost:12345/date 2017年 1月12日 星期四 17时16分56秒 CST ➜ ~ curl http://localhost:12345/date 2017年 1月12日 星期四 17时16分58秒 CST ➜ ~ ➜ ~ curl http://localhost:12345/xx oops ➜ ~ curl http://localhost:12345/yy oops ➜ ~
packetbeat 中的使用
在运行 packetbeat 时,console 上会周期性输出如下日志
2017/01/16 02:40:21.957781 logp.go:230: INFO Non-zero metrics in the last 30s: redis.unmatched_responses=1 libbeat.publisher.published_events=7
对应代码如下
// logExpvars 函数会以 Info 日志级别记录 integer 类型的 expvars 值在 // 指定 interval 内的变化情况;对于每一个 expvar 来说,变化量 delta // 是从 interval 的开始进行记录和计算的; func logExpvars(metricsCfg *LoggingMetricsConfig) { // 没有配置或配置为 false if metricsCfg.Enabled != nil && *metricsCfg.Enabled == false { Info("Metrics logging disabled") return } // 默认 interval 设置为 30s if metricsCfg.Period == nil { metricsCfg.Period = &defaultMetricsPeriod } Info("Metrics logging every %s",metricsCfg.Period) ticker := time.NewTicker(*metricsCfg.Period) prevVals := map[string]int64{} for { <-ticker.C vals := map[string]int64{} // 将注册到 expvar 中的全部 Int 类型内容保存到 vals 中 snapshotExpvars(vals) // 构建 interval 时间内的所有 Int 类型 expvar 变量的 delta 差值字符串 metrics := buildMetricsOutput(prevVals,vals) prevVals = vals if len(metrics) > 0 { Info("Non-zero metrics in the last %s:%s",metricsCfg.Period,metrics) } else { // 指定 interval 内所有 Int 类型的 expvar 变量均无变化 Info("No non-zero metrics in the last %s",metricsCfg.Period) } } }
函数 snapshotExpvars
的实现如下
// snapshotExpvars 对定义的全部 expvars 进行递归处理;针对 integer 类型 // 的内容,会对其 name 和 value 执行 snapshot 操作,保存到一个单独的 // (flat) map 中 func snapshotExpvars(varsMap map[string]int64) { // 遍历当前已全局注册过的 expvar 变量 expvar.Do(func(kv expvar.KeyValue) { switch kv.Value.(type) { // 如果是 Int 类型,直接保存到 map 中 case *expvar.Int: varsMap[kv.Key],_ = strconv.ParseInt(kv.Value.String(),10,64) // 如果是 Map 类型,则递归处理其中的内容(其实是处理其中的 Int 类型内容) case *expvar.Map: snapshotMap(varsMap,kv.Value.(*expvar.Map)) } }) }
函数 buildMetricsOutput
的实现如下
// buildMetricsOutput 负责计算 vals 和 prevVals 的差值 delta ;并构建 // 一个便于打印输出 delta 内容的字符串; // 注:至少存在一个 expvar 变量的 delta 不为 0 才有非空字符串返回 func buildMetricsOutput(prevVals map[string]int64,vals map[string]int64) string { metrics := "" for k,v := range vals { delta := v - prevVals[k] if delta != 0 { metrics = fmt.Sprintf("%s %s=%d",metrics,k,delta) } } return metrics }
在结束 packetbeat 运行时,console 上会输出如下日志
2017/01/16 10:41:44.727504 logp.go:245: INFO Total non-zero values: libbeat.publisher.published_events=12915 redis.unmatched_responses=23 tcp.dropped_because_of_gaps=4 2017/01/16 10:41:44.727537 logp.go:246: INFO Uptime: 1.22785826s
对应的代码如下
func LogTotalExpvars(cfg *Logging) { if cfg.Metrics.Enabled != nil && *cfg.Metrics.Enabled == false { return } vals := map[string]int64{} prevVals := map[string]int64{} snapshotExpvars(vals) metrics := buildMetricsOutput(prevVals,vals) Info("Total non-zero values: %s",metrics) Info("Uptime: %s",time.Now().Sub(startTime)) }