加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

【原创】golang 之 expvar 使用

发布时间:2020-12-16 18:20:44 所属栏目:大数据 来源:网络整理
导读:源码说明 在 expvar.go 中有如下说明: expvar 包提供了一种标准化接口用于 公共变量 ,例如针对 server 中的操作计数器; expvar 以 JSON 格式通过 HTTP 的 /debug/vars 来暴露这些变量; 针对这些公共变量的 set 或 modify 操作具有原子性; 除了会添加 HT

源码说明

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],"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: %vnbody: [%v]n",err,body)
		} else {
			ctr.n = n
			fmt.Fprint(w,"counter resetn")
		}
	}
	fmt.Fprintf(w,"counter = %dn",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 = %sn",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 #%dn",<-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))
}

其他

  • Some notes on Go's expvar package

  • A surprise to watch out for with Go's expvar package (in expvar.Var)

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读