<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    使用 expvar 暴露 Go 程序運行指標

    VSole2022-08-30 14:35:48

    獲取應用程序的運行指標,可以讓我們更好地了解它的實際狀況。將這些指標對接到 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 main
    import (
     _ "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 main
    import (
     "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 庫這么冷門呢?歡迎留言討論。

    stringvar
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    通過common-collection相關gadget,想辦法調用org.mozilla.classfile.DefiningClassLoader這個類去加載字節碼。然后通過T3協議的反序列化漏洞發送給待攻擊weblogic服務器。
    JDK7u21的核心點是我們在cc1中也出現過的AnnotationInvocationHandler,在cc1中我們用到了他會觸發this.memberValues.get(var4);這個點,而在JDK7u21中利用到了他里面的equalsImpl。先來看一下equalsImpl。
    Nim套娃加載.NET程序集
    2021-08-28 23:22:14
    Start the game
    之前看chenx6大佬的博客學習了一下編寫基礎的LLVM Pass,但是那個有很明顯的問題是,作者為了處理Function內部重復引用的多次解密的問題,特判了引用次數,如果存在多處對global string的引用是無法進行混淆的。但是實際的編程中很難不會引用多處字符串,所以那個只能混淆簡單代碼。之后學習了一下pluto-obfuscator項目,里面有一份GlobalEncryption.cpp,借此機會學習一下,順便寫一份New PassManager版本的。runOnModule首先獲取Module的LLVMContext,獲取所有的全局變量,添加到GVs中。
    for 與 foreach 的區別
    2022-04-16 15:01:11
    之前有一個同事突然我問了我一個問題,說在foreach當中能不能刪除list里面的元素,我當時大概說了一下是否能刪除,以及原因;接下來我們來探討一下是否能夠如此;
    JNDI漏洞利用探索
    2022-01-23 19:33:23
    最近學習了淺藍師傅尋找的一些JNDI漏洞的利用鏈受益匪淺,自己也嘗試關于JNDI漏洞利用做一些挖掘,目前JN
    最近想要針對Shiro的利用工具擴展利用鏈,但自己完全寫一個工具即麻煩也沒有必要,因此想要通過SummerSec師傅開源的工具ShiroAttack2擴展來實現,既然要擴展首先就得了解項目的源碼實現。本片文章中我不會通篇的對這個項目代碼進行分析,只抽出幾個我認為的要點進行分析。
    將這些指標對接到 prometheus、zabbix 等監控系統,能夠對應用程序持續檢測,發現異常可以及時告警并得到處理。以 Prometheus 為例,應用程序通過暴露出 HTTP 接口,讓 Prometheus 周期性地通過該接口抓取指標,這就是 Pull。
    JDK1.7 Idea 2020.1 Apache CommonCollections V3.1 Idea默認版本Maven
    CobaltStrike Charset Improvement
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类