您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

使用 expvar 暴露 Go 程序运行指标

2025/7/31 22:42:38发布14次查看
获取应用程序的运行指标,可以让我们更好地了解它的实际状况。将这些指标对接到 prometheus、zabbix 等监控系统,能够对应用程序持续检测,发现异常可以及时告警并得到处理。
pull 与 push与监控系统对接方式有两种,一种是 pull(拉取),另外一种 push(推送)。
以 prometheus 为例,应用程序通过暴露出 http 接口,让 prometheus 周期性地通过该接口抓取指标,这就是 pull。而 push 是应用程序主动将指标推送给 pushgateway, prometheus 则去 pushgateway 抓取数据。
go 标准库中有一个名为 expvar 的包,它的名字由 exp 和 var 两部分组合而成,意味着导出变量。
expvar 为公共变量提供了标准化的接口,并通过 http 以 json 的格式将这些变量暴露出去,很适合采用 pull 的方式与监控系统进行对接。
使用 expvar 库expvar 是标准库,意味着我们并不要额外的依赖,并且它还提供了一些开箱即用的指标。下面我们来学习一下该库的使用。
当引用了 expvar 库(import expvar),以下 init 函数将被自动调用。
func init() { http.handlefunc("/debug/vars", expvarhandler) publish("cmdline", func(cmdline)) publish("memstats", func(memstats))}
该函数为我们注册了 /debug/vars 路径的 http 服务,访问该路径将得到 json 格式的指标。
因此,我们还需要调用 listenandserve 绑定端口,并开始服务 http 请求。
http.listenandserve(":8080", nil)
完整代码如下
package mainimport ( _ "expvar" "net/http")func main() { http.listenandserve(":8080", nil)}
将程序运行起来后,通过 curl 请求,得到以下结果
$ curl localhost:8080/debug/vars{"cmdline": ["/var/folders/xk/gn46n46d503dsztbc6_9qb2h0000gn/t/go-build1657217338/b001/exe/main"],"memstats": {"alloc":278880,"totalalloc":278880,"sys":8735760,"lookups":0,"mallocs":1169,"frees":87,"heapalloc":278880,"heapsys":3866624,"heapidle":2949120,"heapinuse":917504,"heapreleased":2899968,"heapobjects":1082,"stackinuse":327680,"stacksys":327680,"mspaninuse":28696,"mspansys":32640,"mcacheinuse":9600,"mcachesys":15600,"buckhashsys":3875,"gcsys":3826448,"othersys":662893,"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,"numforcedgc":0,"gccpufraction":0,"enablegc":true,"debuggc":false,"bysize":[{"size":0,"mallocs":0,"frees":0},{"size":8,"mallocs":41,"frees":0},{"size":16,"mallocs":496,"frees":0},{"size":24,"mallocs":63,"frees":0},{"size":32,"mallocs":28,"frees":0},{"size":48,"mallocs":134,"frees":0},{"size":64,"mallocs":50,"frees":0},{"size":80,"mallocs":17,"frees":0},{"size":96,"mallocs":17,"frees":0},{"size":112,"mallocs":6,"frees":0},{"size":128,"mallocs":9,"frees":0},{"size":144,"mallocs":9,"frees":0},{"size":160,"mallocs":18,"frees":0},{"size":176,"mallocs":6,"frees":0},{"size":192,"mallocs":0,"frees":0},{"size":208,"mallocs":37,"frees":0},{"size":224,"mallocs":6,"frees":0},{"size":240,"mallocs":0,"frees":0},{"size":256,"mallocs":12,"frees":0},{"size":288,"mallocs":7,"frees":0},{"size":320,"mallocs":2,"frees":0},{"size":352,"mallocs":13,"frees":0},{"size":384,"mallocs":1,"frees":0},{"size":416,"mallocs":30,"frees":0},{"size":448,"mallocs":1,"frees":0},{"size":480,"mallocs":2,"frees":0},{"size":512,"mallocs":0,"frees":0},{"size":576,"mallocs":5,"frees":0},{"size":640,"mallocs":5,"frees":0},{"size":704,"mallocs":3,"frees":0},{"size":768,"mallocs":0,"frees":0},{"size":896,"mallocs":6,"frees":0},{"size":1024,"mallocs":8,"frees":0},{"size":1152,"mallocs":10,"frees":0},{"size":1280,"mallocs":3,"frees":0},{"size":1408,"mallocs":1,"frees":0},{"size":1536,"mallocs":0,"frees":0},{"size":1792,"mallocs":7,"frees":0},{"size":2048,"mallocs":2,"frees":0},{"size":2304,"mallocs":3,"frees":0},{"size":2688,"mallocs":2,"frees":0},{"size":3072,"mallocs":0,"frees":0},{"size":3200,"mallocs":0,"frees":0},{"size":3456,"mallocs":0,"frees":0},{"size":4096,"mallocs":8,"frees":0},{"size":4864,"mallocs":1,"frees":0},{"size":5376,"mallocs":1,"frees":0},{"size":6144,"mallocs":2,"frees":0},{"size":6528,"mallocs":0,"frees":0},{"size":6784,"mallocs":0,"frees":0},{"size":6912,"mallocs":0,"frees":0},{"size":8192,"mallocs":2,"frees":0},{"size":9472,"mallocs":8,"frees":0},{"size":9728,"mallocs":0,"frees":0},{"size":10240,"mallocs":0,"frees":0},{"size":10880,"mallocs":0,"frees":0},{"size":12288,"mallocs":0,"frees":0},{"size":13568,"mallocs":0,"frees":0},{"size":14336,"mallocs":0,"frees":0},{"size":16384,"mallocs":0,"frees":0},{"size":18432,"mallocs":0,"frees":0}]}}
可以看到,expvar 默认已提供了两项指标,分别是程序执行命令(os.args)和运行时内存分配(runtime.memstats)信息。
expvar 库重点内容expvar 中最重要的是 publish 函数和 var 接口。
func publish(name string, v var) {}
publish 函数签名中需要两个参数,name 是我们指定的指标名。例如在上文 expvar 的 init 函数下 publish("cmdline", func(cmdline))代码行,其中cmdline就是指标名,而func(cmdline)则是实现了 var 接口的 expvar.func 类型变量。
var 接口,它只定义了一个 string 方法。需要注意的是,该方法必须要返回一个有效的 json字符串。
// 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}
为了方便使用,expvar 库中提供了五种导出变量类型,它们均实现了 var 接口。
type int struct { i int64}type float struct { f uint64}type map struct { m sync.map // map[string]var keysmu sync.rwmutex keys []string // sorted}type string struct { s atomic.value // string}type func func() any
我们分别通过调用 expvar.newxxx 函数即可完成前四种类型的变量创建与指标注册。
intvar = expvar.newint(“metricname”)floatvar = expvar.newfloat(“metricname”)mapvar = expvar.newmap(“metricname”)stringvar = expvar.newstring(“metricname”)
例如 expvar.newint 函数,它会内部调用 publish 方法完成指标名与 expvar.int 类型变量的绑定。
func newint(name string) *int { v := new(int) publish(name, v) return v}
而 expvar.func 类型,其实是为了让我们可以自定义导出类型。
例如,假如我们想要暴露以下定义的结构体
type mystruct struct { field1 string field2 int field3 float64}
首先需要创建一个数据生成函数。它用于在每次调用 http 服务路径时,通过该函数导出这里面的数据。
func mystructdata() interface{} { return mystruct{ field1: "gopher", field2: 22, field3: 19.99, }}
最后,通过 publish 函数注册指标名即可。
expvar.publish("metricname", expvar.func(mystructdata))
完整示例下面,我们给出一个覆盖五种导出变量类型的完整示例。
package mainimport ( "expvar" "github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/load" "github.com/shirou/gopsutil/v3/mem" "net/http" "time")type load struct { load1 float64 load5 float64 load15 float64}func allloadavg() interface{} { return load{ load1: loadavg(1), load5: loadavg(5), load15: loadavg(15), }}func loadavg(loadnumber int) float64 { avg, _ := load.avg() switch loadnumber { case 5: return avg.load5 case 15: return avg.load15 default: return avg.load1 }}func main() { var ( aliveofseconds = expvar.newint("aliveofseconds") hostid = expvar.newstring("hostid") lastload = expvar.newfloat("lastload") virtualmemory = expvar.newmap("virtualmemory") ) expvar.publish("allloadavg", expvar.func(allloadavg)) h, _ := host.hostid() hostid.set(h) go http.listenandserve(":8080", nil) for { aliveofseconds.add(1) vm, _ := mem.virtualmemory() lastload.set(loadavg(1)) virtualmemory.add("active", int64(vm.active)) virtualmemory.add("buffer", int64(vm.buffers)) time.sleep(1 * time.second) }}
在上述示例中,我们通过 gopsutil 库(介绍可见还在自己写 go 系统监控函数吗一文)获取了一些系统信息,并展示了如何通过 expvar 中的各种变量类型将这些信息进行导出。
curl 访问 localhost:8080/debug/vars 结果如下
$ curl localhost:8080/debug/vars{"aliveofseconds": 1,"allloadavg": {"load1":1.69580078125,"load5":1.97412109375,"load15":1.90283203125},"cmdline": ["/var/folders/xk/gn46n46d503dsztbc6_9qb2h0000gn/t/go-build3566019824/b001/exe/main"],"hostid": "7a1a74f2-30fc-5bc1-b439-6b7aef22e58d","lastload": 1.69580078125,"memstats": {"alloc":256208,"totalalloc":256208,"sys":8735760,"lookups":0,"mallocs":1089,"frees":48,"heapalloc":256208,"heapsys":3866624,"heapidle":2891776,"heapinuse":974848,"heapreleased":2859008,"heapobjects":1041,"stackinuse":327680,"stacksys":327680,"mspaninuse":19992,"mspansys":32640,"mcacheinuse":9600,"mcachesys":15600,"buckhashsys":3905,"gcsys":3851120,"othersys":638191,"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,"numforcedgc":0,"gccpufraction":0,"enablegc":true,"debuggc":false,"bysize":[{"size":0,"mallocs":0,"frees":0},{"size":8,"mallocs":35,"frees":0},{"size":16,"mallocs":415,"frees":0},{"size":24,"mallocs":71,"frees":0},{"size":32,"mallocs":37,"frees":0},{"size":48,"mallocs":141,"frees":0},{"size":64,"mallocs":52,"frees":0},{"size":80,"mallocs":20,"frees":0},{"size":96,"mallocs":23,"frees":0},{"size":112,"mallocs":14,"frees":0},{"size":128,"mallocs":7,"frees":0},{"size":144,"mallocs":7,"frees":0},{"size":160,"mallocs":18,"frees":0},{"size":176,"mallocs":6,"frees":0},{"size":192,"mallocs":1,"frees":0},{"size":208,"mallocs":42,"frees":0},{"size":224,"mallocs":3,"frees":0},{"size":240,"mallocs":0,"frees":0},{"size":256,"mallocs":9,"frees":0},{"size":288,"mallocs":8,"frees":0},{"size":320,"mallocs":5,"frees":0},{"size":352,"mallocs":13,"frees":0},{"size":384,"mallocs":3,"frees":0},{"size":416,"mallocs":33,"frees":0},{"size":448,"mallocs":0,"frees":0},{"size":480,"mallocs":2,"frees":0},{"size":512,"mallocs":1,"frees":0},{"size":576,"mallocs":4,"frees":0},{"size":640,"mallocs":8,"frees":0},{"size":704,"mallocs":3,"frees":0},{"size":768,"mallocs":1,"frees":0},{"size":896,"mallocs":6,"frees":0},{"size":1024,"mallocs":8,"frees":0},{"size":1152,"mallocs":9,"frees":0},{"size":1280,"mallocs":3,"frees":0},{"size":1408,"mallocs":1,"frees":0},{"size":1536,"mallocs":1,"frees":0},{"size":1792,"mallocs":9,"frees":0},{"size":2048,"mallocs":1,"frees":0},{"size":2304,"mallocs":2,"frees":0},{"size":2688,"mallocs":2,"frees":0},{"size":3072,"mallocs":0,"frees":0},{"size":3200,"mallocs":1,"frees":0},{"size":3456,"mallocs":0,"frees":0},{"size":4096,"mallocs":5,"frees":0},{"size":4864,"mallocs":0,"frees":0},{"size":5376,"mallocs":1,"frees":0},{"size":6144,"mallocs":1,"frees":0},{"size":6528,"mallocs":0,"frees":0},{"size":6784,"mallocs":0,"frees":0},{"size":6912,"mallocs":0,"frees":0},{"size":8192,"mallocs":1,"frees":0},{"size":9472,"mallocs":8,"frees":0},{"size":9728,"mallocs":0,"frees":0},{"size":10240,"mallocs":0,"frees":0},{"size":10880,"mallocs":0,"frees":0},{"size":12288,"mallocs":0,"frees":0},{"size":13568,"mallocs":0,"frees":0},{"size":14336,"mallocs":0,"frees":0},{"size":16384,"mallocs":0,"frees":0},{"size":18432,"mallocs":0,"frees":0}]},"virtualmemory": {"active": 1957449728, "buffer": 0}}
总结标准库 expvar 为需要导出的公共变量提供了一个标准化的接口,使用比较简单。
expvar 包内部定义的几种基础类型都相应给出了并发安全的操作方法,我们不需要去重复实现一遍,能够开箱即用。
但是, 在 https://go.dev/ 上统计的公共项目,该库的 import 数量还不足 1万。
相比于其他标准库的 import 数量而言,expvar 的存在感太低了。
以上就是使用 expvar 暴露 go 程序运行指标的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product