【原创】golang 之 expvar 使用

源码说明

expvar.go 中有如下说明:

expvar 包提供了一种标准化接口用于公共变量,例如针对 server 中的操作计数器; expvar 以 JSON 格式通过 HTTP 的 /debug/vars 来暴露这些变量; 针对这些公共变量的 set 或 modify 操作具有原子性;

除了会添加 HTTP handler 以外,该包还会注册如下变量:

  • cmdline os.Args
  • memstats 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

获取由 expvar 提供的内容

➜  ~ 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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,"GCCPUFraction":0,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":15,"Frees":0},{"Size":16,"Mallocs":333,"Frees":0},{"Size":32,"Mallocs":3926,"Frees":0},{"Size":48,"Mallocs":101,"Frees":0},{"Size":64,"Mallocs":30,"Frees":0},{"Size":80,"Mallocs":28,"Frees":0},{"Size":96,"Mallocs":8,"Frees":0},{"Size":112,"Mallocs":4,"Frees":0},{"Size":128,"Mallocs":7,"Frees":0},{"Size":144,"Mallocs":3,"Frees":0},{"Size":160,"Mallocs":16,"Frees":0},{"Size":176,"Mallocs":7,"Frees":0},{"Size":192,"Mallocs":3,"Frees":0},{"Size":208,"Mallocs":24,"Frees":0},{"Size":224,"Mallocs":0,"Frees":0},{"Size":240,"Mallocs":2,"Frees":0},{"Size":256,"Mallocs":9,"Frees":0},{"Size":288,"Mallocs":18,"Frees":0},{"Size":320,"Mallocs":3,"Frees":0},{"Size":352,"Mallocs":9,"Frees":0},{"Size":384,"Mallocs":0,"Frees":0},{"Size":416,"Mallocs":26,"Frees":0},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":1,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":7,"Frees":0},{"Size":640,"Mallocs":2,"Frees":0},{"Size":704,"Mallocs":6,"Frees":0},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":6,"Frees":0},{"Size":1024,"Mallocs":6,"Frees":0},{"Size":1152,"Mallocs":5,"Frees":0},{"Size":1280,"Mallocs":0,"Frees":0},{"Size":1408,"Mallocs":0,"Frees":0},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1664,"Mallocs":6,"Frees":0},{"Size":2048,"Mallocs":16,"Frees":0},{"Size":2304,"Mallocs":4,"Frees":0},{"Size":2560,"Mallocs":0,"Frees":0},{"Size":2816,"Mallocs":0,"Frees":0},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3328,"Mallocs":4,"Frees":0},{"Size":4096,"Mallocs":2,"Frees":0},{"Size":4608,"Mallocs":0,"Frees":0},{"Size":5376,"Mallocs":5,"Frees":0},{"Size":6144,"Mallocs":2,"Frees":0},{"Size":6400,"Mallocs":0,"Frees":0},{"Size":6656,"Mallocs":1,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":0,"Frees":0},{"Size":8448,"Mallocs":0,"Frees":0},{"Size":8704,"Mallocs":0,"Frees":0},{"Size":9472,"Mallocs":0,"Frees":0},{"Size":10496,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14080,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":16640,"Mallocs":0,"Frees":0},{"Size":17664,"Mallocs":0,"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.Name, 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.Key, 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))
}

其他

转载于:https://my.oschina.net/moooofly/blog/826460

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值