使用go语言中的内置库调试性能

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
性能测试 PTS,5000VUM额度
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 【5月更文挑战第21天】本文介绍Go 语言提供了内置的 expvar 模块来输出度量数据,帮助定位性能瓶颈。与 pprof 不同,expvar 专注于应用的宏观状态,通过 HTTP 接口 `/debug/vars` 提供标准的 JSON 格式数据,包括自定义度量和内存统计等。通过 expvar,开发者可以轻松监控应用状态,如消息处理速率、内存使用等,而无需像 C++ 或 Java 那样手动实现。

简介

本文主要基于expvar和pprof做性能对比。expvar 提供了 Var 接口,允许开发者定义自己的性能指标,这些指标会自动暴露给外部工具。Go 标准库已包含 expvar 的初始化,自动注册 HTTP 手柄。

1 expvar 输出度量数据。辅助定位性能瓶颈

对不同类型的性能数据进行收集和采样,go提供了内置的模块和方法。

比如pprof 和 expvar,在微观层面,采样通过运行性能基准测试收集和采样数据的方法,这种方法适用于定位函数或方法实现中存在性能瓶颈点的情形。

在宏观层面,采用独立程序进行性能数据采样,往往很难快速捕捉到真正的瓶颈,尤其对那些内部结构复杂,业务逻辑过多,内部较多并发的程序。

我们在对这样的程序进行性能采样时,真正的瓶颈可能被其他数据覆盖。

那么如何能更高效捕捉到应用的性能瓶颈呢,我们需要知道go程序运行的状态。

应用运行一般状态以度量数据的形式呈现。通过了解应用关键路径的度量数据,我们可以确定某个度量点的应用性能是否符合预期性能指标。

这样可以最大程度缩小性能瓶颈搜索范围。 从而快速定位应用的瓶颈点进行优化。

这些可以反映应用运行状态的数据也被成为应用的内省 introspection 数据。

相比通过查询外部特征获取的 探针类 probing 数据(比如查看应用某个端口是否有响应并返回正确的数据或状态码),内省数据可以传达更丰富,更多有关应用程序状态的上下文信息。

这些上下文信息可以是应用对各类资源的占用信息,比如应用运行了多少内存空间,可以是自定义的性能指标信息,比如单位时间处理外部请求数量,应答延迟,队列积压量,等等。

在c++ java中,并没有内置输出应用状态度量数据的设施 ,接口方法,指标定义方法,数据输出格式等等,需要开发者自己通过编码实现或利用第三方库实现。

go自带电池的编码语言,我们可以轻松使用go标准库提供的 expvar 包按统一接口,统一数据格式,一致的指标定义方法输出自定义度量数据。 我们一起看看如何使用 expvar 输出自定义性能度量数据。

2 expvar 工作原理

go标准库的expvat 包提供了一个输出应用程序状态信息的标准化方案,它包括以下3个内容:

  1  输出数据的接口形式
  2  输出数据的编码格式
  3  用户自定义性能指标方法

go 应用通过expvar 包输出内部状态信息的工作原理如下

  ------------------------
  |    go app            |                       http get  cmd
  |  import _ "expvar"   | ---(/debug/vars) ---> http get web  
  |  var ( ... )         |                       cmdline: ["xx"]
  |                      |                       memstats: {...}
  |                      |                       msg_in:6
  ------------------------                       msg_out:5
                                                 custom_var:xx
  expvar 标准库
  type Var interface {
    String() string
   }

  func init() {
    http.HandleFunc("/debug/vars", expvarHandler)
  }

从上图知道,go应用如果需要输出自身状态数据,需要如下形式导出

  import _ expvar

与net/http/pprof 类似,expvar 包也在自己的initial函数向http包的默认请求路由器 defaultServeMux注册一个服务端点 /debug/vars

  //$GOROOT/src/expvar/expvar.go
  func init() {
     http.HandleFunc("/debug/vars", expvarHandler)
     ...
  }

这个服务端点就是 expvar 提供给外部的获取应用内部状态的唯一标准接口,

外部工具(无论命令行还是基于web的图形化程序)都可以通过标准的http get 请求从该服务器端点获取内部状态数据,比如

  //expvar_demo1.go
  package main


  import (
    "expvar"
    _ "expvar"
    "fmt"
    "io"
    "net/http"
  )

  var (
    msgln      * expvar.Int
    msgOut     * expvar.Float
    customeVar * CustomVar
  )

  type CustomVar struct{}

  // type Var interface {
  //  String() string
  // }

  func (cv * CustomVar) String() string {
    return "ok"
  }

  func init() {
    msgln = expvar.NewInt("msg_in")
    msgOut = expvar.NewFloat("msg_out")
    customeVar = &CustomVar{}
    expvar.Publish("custom_var", customeVar)
  }

  func HomeHandler(w http.ResponseWriter, r * http.Request) {
    // w.Write([]byte("welcome.!"))
    io.WriteString(w, "welcome.!")
  }
  func main() {
    serInfo := "localhost:8801"
    http.HandleFunc("/home", HomeHandler)
    fmt.Printf("serInfo:%v \n", serInfo)
    fmt.Println(http.ListenAndServe(serInfo, nil))
  }

启动该服务后,访问首页,/home 返回 字符welcome,访问debug 信息 http://127.0.0.1:8809/debug/vars

 {
"cmdline": ["4031153171\\b001\\exe\\main.exe"],
"custom_var": ok,
"memstats": {"Alloc":256984,"TotalAlloc":256984,"Sys":11190480,"Lookups":0,"Mallocs":1438,"Frees":95,"HeapAlloc":256984,"HeapSys":4030464,"HeapIdle":2818048,"HeapInuse":1212416,"HeapReleased":2818048,"HeapObjects":1343,"StackInuse":163840,"StackSys":163840,"MSpanInuse":29376,"MSpanSys":32640,"MCacheInuse":9344,"MCacheSys":16352,"BuckHashSys":6277,"GCSys":6025192,"OtherSys":915715,"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":21,"Frees":0},{"Size":16,"Mallocs":486,"Frees":0},{"Size":24,"Mallocs":110,"Frees":0},{"Size":32,"Mallocs":93,"Frees":0},{"Size":48,"Mallocs":329,"Frees":0},{"Size":64,"Mallocs":40,"Frees":0},{"Size":80,"Mallocs":28,"Frees":0},{"Size":96,"Mallocs":20,"Frees":0},{"Size":112,"Mallocs":10,"Frees":0},{"Size":128,"Mallocs":8,"Frees":0},{"Size":144,"Mallocs":6,"Frees":0},{"Size":160,"Mallocs":21,"Frees":0},{"Size":176,"Mallocs":7,"Frees":0},{"Size":192,"Mallocs":0,"Frees":0},{"Size":208,"Mallocs":32,"Frees":0},{"Size":224,"Mallocs":5,"Frees":0},{"Size":240,"Mallocs":6,"Frees":0},{"Size":256,"Mallocs":12,"Frees":0},{"Size":288,"Mallocs":7,"Frees":0},{"Size":320,"Mallocs":4,"Frees":0},{"Size":352,"Mallocs":10,"Frees":0},{"Size":384,"Mallocs":1,"Frees":0},{"Size":416,"Mallocs":19,"Frees":0},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":0,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":3,"Frees":0},{"Size":640,"Mallocs":10,"Frees":0},{"Size":704,"Mallocs":4,"Frees":0},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":1,"Frees":0},{"Size":1024,"Mallocs":14,"Frees":0},{"Size":1152,"Mallocs":4,"Frees":0},{"Size":1280,"Mallocs":3,"Frees":0},{"Size":1408,"Mallocs":4,"Frees":0},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1792,"Mallocs":3,"Frees":0},{"Size":2048,"Mallocs":2,"Frees":0},{"Size":2304,"Mallocs":2,"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":4,"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":1,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":18432,"Mallocs":0,"Frees":0}]},
"msg_in": 0,
"msg_out": 0
}

如果应用程序本身并没有使用默认 路由器 DefaultServeMux 那么就需要人工将 expvar的服务端点注册到应用程序的路由器。
expvar 包提供了Handler 函数,可以用于内部expvarHandler注册

  //expvar_demo2.go
  package main

返回的数据如下,为JSON格式,包括cmdline,memstats两个类别的数据。

  {
      "cmdline": ["60480842\\b001\\exe\\main.exe"],
      "memstats": {"Alloc":1782760,"TotalAlloc":3203056,"Sys":16372128,"Lookups":0,"Mallocs":26440,"Frees":15554,"HeapAlloc":1782760,"HeapSys":8028160,"HeapIdle":4046848,"HeapInuse":3981312,"HeapReleased":2932736,"HeapObjects":10886,"StackInuse":360448,"StackSys":360448,"MSpanInuse":99144,"MSpanSys":114240,"MCacheInuse":9344,"MCacheSys":16352,"BuckHashSys":6974,"GCSys":6738048,"OtherSys":1107906,"NextGC":4194304,"LastGC":1674791690593938100,"PauseTotalNs":18400,"PauseNs":[18400,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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":[1674791690593938100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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":1,"NumForcedGC":0,"GCCPUFraction":0.012216102957431063,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":530,"Frees":243},{"Size":16,"Mallocs":8649,"Frees":4292},{"Size":24,"Mallocs":814,"Frees":164},{"Size":32,"Mallocs":2580,"Frees":1517},{"Size":48,"Mallocs":2430,"Frees":702},{"Size":64,"Mallocs":949,"Frees":389},{"Size":80,"Mallocs":390,"Frees":292},{"Size":96,"Mallocs":143,"Frees":19},{"Size":112,"Mallocs":4344,"Frees":3677},{"Size":128,"Mallocs":351,"Frees":316},{"Size":144,"Mallocs":52,"Frees":17},{"Size":160,"Mallocs":577,"Frees":268},{"Size":176,"Mallocs":19,"Frees":8},{"Size":192,"Mallocs":71,"Frees":53},{"Size":208,"Mallocs":130,"Frees":48},{"Size":224,"Mallocs":72,"Frees":55},{"Size":240,"Mallocs":31,"Frees":21},{"Size":256,"Mallocs":58,"Frees":21},{"Size":288,"Mallocs":89,"Frees":41},{"Size":320,"Mallocs":234,"Frees":175},{"Size":352,"Mallocs":35,"Frees":13},{"Size":384,"Mallocs":19,"Frees":6},{"Size":416,"Mallocs":76,"Frees":16},{"Size":448,"Mallocs":10,"Frees":4},{"Size":480,"Mallocs":4,"Frees":3},{"Size":512,"Mallocs":62,"Frees":11},{"Size":576,"Mallocs":87,"Frees":11},{"Size":640,"Mallocs":234,"Frees":76},{"Size":704,"Mallocs":27,"Frees":8},{"Size":768,"Mallocs":13,"Frees":1},{"Size":896,"Mallocs":25,"Frees":15},{"Size":1024,"Mallocs":75,"Frees":18},{"Size":1152,"Mallocs":32,"Frees":12},{"Size":1280,"Mallocs":67,"Frees":32},{"Size":1408,"Mallocs":4,"Frees":2},{"Size":1536,"Mallocs":9,"Frees":8},{"Size":1792,"Mallocs":27,"Frees":13},{"Size":2048,"Mallocs":17,"Frees":9},{"Size":2304,"Mallocs":3,"Frees":0},{"Size":2688,"Mallocs":44,"Frees":14},{"Size":3072,"Mallocs":7,"Frees":1},{"Size":3200,"Mallocs":2,"Frees":0},{"Size":3456,"Mallocs":9,"Frees":7},{"Size":4096,"Mallocs":29,"Frees":5},{"Size":4864,"Mallocs":6,"Frees":2},{"Size":5376,"Mallocs":20,"Frees":9},{"Size":6144,"Mallocs":8,"Frees":0},{"Size":6528,"Mallocs":1,"Frees":1},{"Size":6784,"Mallocs":0,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":9,"Frees":0},{"Size":9472,"Mallocs":14,"Frees":3},{"Size":9728,"Mallocs":3,"Frees":1},{"Size":10240,"Mallocs":0,"Frees":0},{"Size":10880,"Mallocs":9,"Frees":2},{"Size":12288,"Mallocs":0,"Frees":0},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14336,"Mallocs":1,"Frees":0},{"Size":16384,"Mallocs":1,"Frees":0},{"Size":18432,"Mallocs":1,"Frees":0}]}
      }

在默认返回的状态数据中,包含了 cmdline 和 memsatts。 这两个输出数据是 expvar 在init 函数就发布过的publish的变量。

  //$GOROOT/src/expvar/expvar.go

  func init() {
  http.HandleFunc("/debug/vars", expvarHandler)
    Publish("cmdline",Func(cmdline))
    Publish("memstats", Func(memstats))
  }

cmdline 字段含义是输出数据的应用名,这里因为通过go run 运行的应用,所以cmdline值是一个临时路径的应用。
而memstats输出的数据对应的是runtime.Memstats结构体,反映的是应用在运行期间堆内存分配,栈内存分配和GC状态。

runtime.Memstats结构体的字段可能会随着Go版本演进而变化,字段具体含义参考 Memstats的注释。 $GOROOT/src/expvar/expvar.go

3 输出自定义度量数据

  • expvar 自定义度量数据输出

    expvar 包为go应用输出内部状态的标准化方案,

    1, 接口化标准(默认为debug/vars接口获取数据);
    2, 标准数据编码Json;
    3, 自定义输出度量数据的标准方法;

    从debug/var中获取的JSON数据有名称为 custom_var 的字段,这是一个自定义的度量数据接口 expvar String()。

    全部实现了方法 String() 的结构体,都可以被发布并作为输出的应用内部状态的一部分。

    type cusToms struct {
      Value int64
    }
    
    func (ct *cusToms) String() string {
      ss := strconv.FormatInt(atomic.LoadInt64(&ct.Value), 10)  
      return ss
    }
    
    func (ct *cusToms) Add(delta int64) {
      atomic.AddInt64(&ct.Value, delta)
    }
    
    func (ct *cusToms) Set(value int64) {
      atomic.StoreInt64(&ct.Value, value)
    }
    

    除了go标准中接口需要实现的 String(),这里还实现了 Add 和 Set 用于新增和修改。
    通过Add对该自定义指标自增某一个量,也可以通过Set重置该指标。

    我们在设计能反映Go应用内部状态的自定义指标时,经常设计以下两个类型的指标:

    • 测量型

      这类指标是数字,支持上下增减。我们定期获取该指标的快照。常见的CPU,内存使用都是这个类型。

      在业务层面,当前网站的在线访客数据,当前业务系统平均响应延迟都属于这类指标。

    • 计数型

      这类指标也是数字,它的特点是随着时间推移,数值不会减少。虽然它们永远不会减少,但有时可以重置为0.

      它们再次开始递增,如:系统正常运行时间,某端口收发包字节数,24消失入队列的消息数量。

      计数的优势在可以用于计算变化率: 将 T + 1 时刻获取的指标值与T时刻的指标值做比较,即可获得两个时刻之间的变化。

这两类常见指标,我们无需像之前的 CustomVar那样自行实现,expvar 包提供了对常用指标类型的原生支持。

1.4 实例: 度量指标:比如整型,浮点数指标

以及像memstats 那样的 Map型复合指标。 比如整型指标

    type Int struct {
      V int64
    }

    func (ct *Int) Value() int64 {
       return atomic.LoadInt64(&ct.V)
    } 

    func (ct *Int) String() string {
      ss := strconv.FormatInt(atomic.LoadInt64(&ct.V), 10)  
      return ss
    }

    func (ct *Int) Add(delta int64) {
      atomic.AddInt64(&ct.V, delta)
    }

    func (ct *Int) Set(value int64) {
      atomic.StoreInt64(&ct.V, value)
    }

Int 类型在满足Var接口的同时,还实现了 Add,Set Value 方法,方便我们使用它创建测量型,计数型指标,并且对其值的修改是并发安全的。

针对expvar.Int类型,expvar也提供了创建即发布的NewInt函数,这样我们无需再调用Publish函数发布指标

    func NewInt(name string) *Int {
      v := new(Int)
      Publish(name, v)
      return v
    }

将 之前的 customVar 指标改为使用 expvar.Int 实现:

  var customVar * expvar.Int

   func init() {
      cusToms = * expvar.NewInt("customVar")
      cusToms.Set(time.Now().UnixMilli())
    }

    router.GET("/debug/vars", CtxExpHand)

    //模拟一个后台任务 ,计数加1 耗时100 毫秒
    go func() {
      for {
        cusToms.Add(1)
        time.Sleep(time.Millisecond * 100)
      }
    }()

使用内置的类型 expvar.Int 时,代码更简洁。

  • 复合类型的 自定义性能数据

    expvar.Map 类型定义一个像memstats那样的复合指标

    性能计数:使用内置的类型

    var cusTomInt *expvar.Int
    

性能计数:复合性能数据

    var custMap *expvar.Map

    func init() {

性能计数

      cusTomInt = expvar.NewInt("customVar")
      cusTomInt.Set(1)

性能复合数据

      custMap = expvar.NewMap("customMap")
      var fieldOne expvar.Int
      var fieldTwo expvar.Float
      custMap.Set("fieldOne", &fieldOne)
      custMap.Set("fieldTwo", &fieldTwo)

如果完全自己定义的 性能指标,则需要人工 Publish

      // expvar.Publish("custom_var", &cusToms{time.Now().UnixMilli()})

    }

在路由注册,或 在接口中 模拟后台任务性能计数

模拟一个后台任务 100 毫秒, 直到返回

      go func() {
        for {
          cusTomInt.Add(1)
          custMap.Add("fieldOne", 1)
          custMap.AddFloat("fieldTwo", 0.01)
          time.Sleep(time.Millisecond * 100)
        }
      }()

调用debug/vars 返回这两个参数的数据

  "customMap": {"fieldOne": 41, "fieldTwo": 0.4100000000000002},
  "customVar": 42,    

如上,一个expvar.Map 类型变量定义后,可以向该复合指标变量添加指标,比较示例的 fieldOne。

在业务逻辑中,可以通过 expvar.Map 提供的 Add,AddFloat 方法对复合指标内部单个指标进行更新。

如果希望把一个结构体类型当作一个复合指标直接输出,expvar 也提供了很好的支持。

将结构体体作为复合类型直接输出

  type CustomVarStruct struct {
     FieldOne  int64  `json:"field_one" `
     FieldTwo float64 `json:"field_two"`
  }

  var (
     field_one expvar.Int
     field_two expvar.Float
  )

  func exportStruct() interface{} {
      return CustomVarStruct{
        FieldOne: field_one.Value(),
        FieldTwo: field_two.Value(),
    }
}

发布到接口

 func init() {

将结构体直接输出为性能数据

     expvar.Publish("CustomVarStruct", expvar.Func(exportStruct))

 }

在路由注册或接口中

模拟一个后台任务 100 毫秒, 直到返回。

    go func() {
      for {

输出 整型性能数据:

        cusTomInt.Add(1)

输出 复合类型 性能数据

        custMap.Add("fieldOne", 1)
        custMap.AddFloat("fieldTwo", 0.01)

输出结构体信息

        field_one.Add(1)
        field_two.Add(0.01)

        time.Sleep(time.Millisecond * 100)
      }
    }()

调用接口,结构体信息将作为 字典返回

  "CustomVarStruct": {"field_one":43,"field_two":0.4300000000000002},

4 小结

我们看到,针对结构体类型,expvar 包并未提供整型,浮点型,Map类型那样的直接支持。

而是通过实现一个 返回 interface{}的函数(这里是 exportStruct),并通过Publish 函数将该函数发布出去。

   expvar.Func(exportStruct)

这个返回 interface{} 类型的函数的返回值底层类型必须是一个支持序列号的JSON格式的类型。

在上面的示例中,这个返回值底层类型为 CustomVarStruct 结构体,该类型本身支持被序列化为一个JSON 文件

通过这种结构体通用方法可以发布任何类型的自定义指标。

目录
相关文章
|
2天前
|
存储 Cloud Native Shell
go库介绍:Golang中的Viper库
Viper 是 Golang 中的一个强大配置管理库,支持环境变量、命令行参数、远程配置等多种配置来源。本文详细介绍了 Viper 的核心特点、应用场景及使用方法,并通过示例展示了其强大功能。无论是简单的 CLI 工具还是复杂的分布式系统,Viper 都能提供优雅的配置管理方案。
|
2天前
|
Go
go语言的复数常量
【10月更文挑战第21天】
13 6
|
2天前
|
Go
go语言的浮点型常量
【10月更文挑战第21天】
9 4
|
2天前
|
编译器 Go
go语言的整型常量
【10月更文挑战第21天】
8 3
|
2天前
|
Serverless Go
Go语言中的并发编程:从入门到精通
本文将深入探讨Go语言中并发编程的核心概念和实践,包括goroutine、channel以及sync包等。通过实例演示如何利用这些工具实现高效的并发处理,同时避免常见的陷阱和错误。
|
3天前
|
安全 Go 开发者
代码之美:Go语言并发编程的优雅实现与案例分析
【10月更文挑战第28天】Go语言自2009年发布以来,凭借简洁的语法、高效的性能和原生的并发支持,赢得了众多开发者的青睐。本文通过两个案例,分别展示了如何使用goroutine和channel实现并发下载网页和构建并发Web服务器,深入探讨了Go语言并发编程的优雅实现。
10 2
|
6月前
|
开发框架 安全 中间件
Go语言开发小技巧&易错点100例(十二)
Go语言开发小技巧&易错点100例(十二)
74 1
|
4天前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
17 3
|
6天前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
14 3
|
3月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
122 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库