在golang中度量程序性能

举报
码乐 发表于 2024/03/12 10:42:14 2024/03/12
【摘要】 1 如何评估golang程序的性能对不同类型的性能数据进行收集和采样,go提供了内置的模块和方法。比如pprof 和 expvar,在微观层面,采样通过运行性能基准测试收集和采样数据的方法,这种方法适用于定位函数或方法实现中存在性能瓶颈点的情形。 1.1 expvar 输出度量数据。辅助定位性能瓶颈在宏观层面,采用独立程序进行性能数据采样,往往很难快速捕捉到真正的瓶颈,尤其对那些内部结构...

1 如何评估golang程序的性能

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

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

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

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

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

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

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

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

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

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

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

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

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

1.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

1.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},

2 小结

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

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

   expvar.Func(exportStruct)

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

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

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

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19 天,点击查看活动详情

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。