如何使用go自定义prometheus的exporter

其他教程   发布日期:2024年05月07日   浏览次数:364

这篇文章主要介绍“如何使用go自定义prometheus的exporter”,在日常操作中,相信很多人在如何使用go自定义prometheus的exporter问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用go自定义prometheus的exporter”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

介绍

  1. prometheus
中如果要监控服务器和应用的各种指标,需要用各种各样的
  1. exporter
服务,例如
  1. node_exportes
  1. mysql_exportes
  1. pgsql_exportes
等。这些都是官方或者第三方已经提供好的。但是如果自己想要监控一些其它
  1. exportes
没有的指标,则就需要自己去构建一个属于自己的
  1. exportes
,好在官方提供相关的库,目前支持以下语言:

官方支持语言:

  • Go

  • Java or Scala

  • Python

  • Ruby

  • Rust

metric的类型

在开始之前需要了解下

  1. metric
的类型划分
    1. Counter(计数器)
    :只增不减的计数器,用于记录事件发生的次数,例如请求数量、错误数量等。
    1. Gauge(仪表盘)
    :可增可减的指标,用于记录当前的状态,例如 CPU 使用率、内存使用量等。
    1. Histogram(直方图)
    :用于记录数据的分布情况,例如请求响应时间的分布情况。
    1. Summary(摘要)
    :与 Histogram 类似,但是它会在客户端计算出一些摘要信息,例如平均值、标准差等。

类型详解

Guage

  1. Gauge的特点:
  2. 1. 可以任意上升或下降,没有固定的范围限制。
  3. 2. 可以被设置为任何值,不像Counter只能递增。
  4. 3. 可以被用来表示瞬时值或累计值。
  5. 4. 可以被用来表示单个实体的状态,例如单个服务器的CPU使用率。
  6. 5. 可以被用来表示多个实体的总体状态,例如整个集群的CPU使用率。
  7. Gauge的使用:
  8. 1. Gauge的值可以通过set()方法进行设置。
  9. 2. Gauge的值可以通过inc()和dec()方法进行增加或减少。
  10. 3. Gauge的值可以通过add()方法进行增加或减少指定的值。
  11. 4. Gauge的值可以通过set_to_current_time()方法设置为当前时间戳。
  12. 5. Gauge的值可以通过observe()方法进行设置,这个方法可以用来记录样本值和时间戳。

Counter

  1. Counter的特点:
  2. 1. Counter只能增加,不能减少或重置。
  3. 2. Counter的值是一个非负整数。
  4. 3. Counter的值可以随时间增加,但不会减少。
  5. 4. Counter的值在重启Prometheus时会重置为0
  6. 5. Counter的值可以被多个Goroutine同时增加,不需要加锁。
  7. 6. Counter的值可以被推送到Pushgateway中,用于监控非Prometheus监控的数据。
  8. Counter的使用方法:
  9. 1. 在程序中定义一个Counter对象,并初始化为0
  10. 2. 当需要记录计数时,调用CounterInc()方法增加计数器的值。
  11. 3. Counter对象暴露给Prometheus,使其能够收集数据。
  12. 4. Prometheus中定义一个相应的指标,并将Counter对象与该指标关联。

示例代码:

  1. import (
  2. "github.com/prometheus/client_golang/prometheus"
  3. "github.com/prometheus/client_golang/prometheus/promauto"
  4. )
  5. // 定义一个Counter对象
  6. var requestCounter = promauto.NewCounter(prometheus.CounterOpts{
  7. Name: "http_requests_total",
  8. Help: "The total number of HTTP requests",
  9. })
  10. // 记录请求计数
  11. func handleRequest() {
  12. requestCounter.Inc()
  13. // 处理请求
  14. }

在上面的代码中,我们定义了一个名为

  1. http_requests_total
  1. Counter
对象,用于记录
  1. HTTP
请求的总数。每当处理一个请求时,我们调用
  1. requestCounter.Inc()
方法增加计数器的值。最后,我们将
  1. Counter
对象暴露给
  1. Prometheus
,并在
  1. Prometheus
中定义了一个名为
  1. http_requests_total
的指标,将
  1. Counter
对象与该指标关联。这样,
  1. Prometheus
就能够收集和展示
  1. http_requests_total
指标的数据了

Histogram

  1. Histogram是一种Prometheus指标类型,用于度量数据的分布情况。它将数据分成一系列桶(bucket),每个桶代表一段范围内的数据。每个桶都有一个计数器(counter),用于记录该范围内的数据数量。在Prometheus中,Histogram指标类型的名称以“_bucket”结尾。
  2. Histogram指标类型通常用于度量请求延迟、响应大小等连续型数据。例如,我们可以使用Histogram指标类型来度量Web应用程序的请求延迟。我们可以将请求延迟分成几个桶,例如0.1秒、0.5秒、1秒、5秒、10秒、30秒等。每个桶都记录了在该范围内的请求延迟的数量。
  3. Histogram指标类型还有两个重要的计数器:sumcountsum用于记录所有数据的总和,count用于记录数据的数量。通过这两个计数器,我们可以计算出平均值和其他统计信息。
  4. Prometheus中,我们可以使用histogram_quantile函数来计算某个百分位数的值。例如,我们可以使用histogram_quantile(0.9, my_histogram)来计算my_histogram指标类型中90%的请求延迟的值。
  5. 总之,Histogram指标类型是一种非常有用的指标类型,可以帮助我们了解数据的分布情况,从而更好地监控和优化应用程序的性能。

Summary

  1. SummaryPrometheus中的一种指标类型,用于记录一组样本的总和、计数和分位数。它适用于记录耗时、请求大小等具有较大变化范围的指标。
  2. Summary指标类型包含以下几个指标:
  3. 1. sum:样本值的总和。
  4. 2. count:样本值的计数。
  5. 3. quantile:分位数。
  6. 其中,sumcount是必须的,而quantile是可选的。
  7. 在使用Summary指标类型时,需要注意以下几点:
  8. 1. 每个Summary指标类型都会记录所有样本的总和和计数,因此它们的值会随时间变化而变化。
  9. 2. 每个Summary指标类型都可以记录多个分位数,例如50%、90%、95%、99%等。
  10. 3. 每个Summary指标类型都可以设置一个时间窗口,用于计算分位数。
  11. 4. 每个Summary指标类型都可以设置一个最大样本数,用于限制内存使用。
  12. 5. 每个Summary指标类型都可以设置一个标签集,用于区分不同的实例。
  13. 总之,Summary指标类型是一种非常有用的指标类型,可以帮助我们更好地了解系统的性能和健康状况

示例

以下示例实现了通过传入的端口号监听对应的进程,并输出进程的一些信息,如pid、cmdline、exe、ppid、内存使用等信息(通过读/proc/pid/目录下的文件来实现),后面如果有其他需要可自行修改。因为写的比较仓促,这里也不详细介绍代码中的含义,有兴趣的可以留言,或者直接拿走代码试试。

目录结构是

  1. |-main.go
  2. |-go.mod
  3. |-go.sum
  4. |-collector
  5. |-- exec.go
  6. |-- port.go

main.go

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "time"
  6. "exporter/collector"
  7. "github.com/alecthomas/kingpin"
  8. "github.com/prometheus/client_golang/prometheus"
  9. "github.com/prometheus/client_golang/prometheus/promhttp"
  10. )
  11. // 定义命令行参数
  12. var (
  13. ticker = kingpin.Flag("ticker", "Interval for obtaining indicators.").Short('t').Default("5").Int()
  14. mode = kingpin.Flag("mode", "Using netstat or lsof for specified port pid information.").Short('m').Default("netstat").String()
  15. port = kingpin.Flag("port", "This service is to listen the port.").Short('p').Default("9527").String()
  16. ports = kingpin.Arg("ports", "The process of listening on ports.").Required().Strings()
  17. )
  18. func main() {
  19. kingpin.Version("1.1")
  20. kingpin.Parse()
  21. // 注册自身采集器
  22. prometheus.MustRegister(collector.NewPortCollector(*ports, *mode))
  23. // fmt.Printf("Would ping: %s with timeout %s
  24. ", *mode, *ports)
  25. go func() {
  26. for {
  27. collector.NewPortCollector(*ports, *mode).Updata()
  28. time.Sleep(time.Duration(*ticker) * time.Second)
  29. }
  30. }()
  31. http.Handle("/metrics", promhttp.Handler())
  32. fmt.Println("Ready to listen on port:", *port)
  33. if err := http.ListenAndServe("0.0.0.0:"+*port, nil); err != nil {
  34. fmt.Printf("Error occur when start server %v", err)
  35. }
  36. }

exec.go

  1. package collector
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. )
  10. var (
  11. order int
  12. awkMap = make(map[int]string)
  13. result = make(map[string]string)
  14. // 定义要在status文件里筛选的关键字
  15. targetList = []string{"Name", "State", "PPid", "Uid", "Gid", "VmHWM", "VmRSS"}
  16. targetResult = make(map[string]map[string]string)
  17. )
  18. func stringGrep(s string, d string) (bool, error) {
  19. for k, v := range d {
  20. if v != rune(s[k]) {
  21. return false, fmt.Errorf("string does not match")
  22. }
  23. }
  24. order = 1
  25. resolv, err := stringAWK(s[len(d):])
  26. if len(resolv) == 0 {
  27. return false, err
  28. }
  29. order = 0
  30. return true, nil
  31. }
  32. func stringAWK(s string) (map[int]string, error) {
  33. i := 0
  34. for k, v := range s {
  35. if v != rune(9) && v != rune(32) && v != rune(10) {
  36. i = 1
  37. awkMap[order] += string(v)
  38. } else {
  39. if i > 0 {
  40. order++
  41. i = 0
  42. }
  43. stringAWK(s[k+1:])
  44. return awkMap, nil
  45. }
  46. }
  47. return awkMap, fmt.Errorf("awk error")
  48. }
  49. func GetProcessInfo(p []string, m string) map[string]map[string]string {
  50. for _, port := range p {
  51. // 通过端口号获取进程pid信息
  52. // 通过组合命令行的方式执行linux命令,筛选出pid
  53. cmd := "sudo " + m + " -tnlp" + "|grep :" + port + "|awk '{print $NF}'|awk -F'/' '{print $1}'"
  54. getPid := exec.Command("bash", "-c", cmd)
  55. out, err := getPid.Output()
  56. if err != nil {
  57. fmt.Println("exec command failed", err)
  58. return nil
  59. }
  60. dir := strings.ReplaceAll(string(out), "
  61. ", "")
  62. if len(dir) == 0 {
  63. fmt.Println("'dir' string is empty")
  64. return nil
  65. // panic("'dir' string is empty")
  66. }
  67. // fmt.Println("test_dir", dir)
  68. result["pid"] = dir
  69. // 获取命令行绝地路径
  70. cmdRoot := "sudo ls -l /proc/" + dir + "/exe |awk '{print $NF}'"
  71. getCmdRoot := exec.Command("bash", "-c", cmdRoot)
  72. out, err = getCmdRoot.Output()
  73. if err != nil {
  74. fmt.Println("exec getCmdRoot command failed", err)
  75. }
  76. // fmt.Println("test_cmdroot", strings.ReplaceAll(string(out), "
  77. ", ""))
  78. result["cmdroot"] = strings.ReplaceAll(string(out), "
  79. ", "")
  80. // 获取/proc/pid/cmdline文件内信息
  81. cmdline, err := os.Open("/proc/" + dir + "/cmdline")
  82. if err != nil {
  83. fmt.Println("open cmdline file error :", err)
  84. panic(err)
  85. }
  86. cmdlineReader, err := bufio.NewReader(cmdline).ReadString('
  87. ')
  88. if err != nil && err != io.EOF {
  89. fmt.Println(err)
  90. }
  91. result["cmdline"] = strings.ReplaceAll(cmdlineReader, "x00", " ")
  92. // 获取/proc/pid/status文件内信息
  93. status, err := os.Open("/proc/" + dir + "/status")
  94. if err != nil {
  95. fmt.Println("open status file error :", err)
  96. }
  97. // 执行函数返回前关闭打开的文件
  98. defer cmdline.Close()
  99. defer status.Close()
  100. statusReader := bufio.NewReader(status)
  101. if err != nil {
  102. fmt.Println(err)
  103. }
  104. for {
  105. line, err := statusReader.ReadString('
  106. ') //注意是字符
  107. if err == io.EOF {
  108. if len(line) != 0 {
  109. fmt.Println(line)
  110. }
  111. break
  112. }
  113. if err != nil {
  114. fmt.Println("read file failed, err:", err)
  115. // return
  116. }
  117. for _, v := range targetList {
  118. istrue, _ := stringGrep(line, v)
  119. if istrue {
  120. result[v] = awkMap[2]
  121. // fmt.Printf("%v结果是:%v
  122. ", v, awkMap[2])
  123. awkMap = make(map[int]string)
  124. }
  125. }
  126. }
  127. // fmt.Println("数据的和:", result)
  128. // fmt.Println("test_result", result)
  129. targetResult[port] = result
  130. // 给result map重新赋值,要不然使用的是同一个map指针,targetResult结果是一样的
  131. result = make(map[string]string)
  132. }
  133. // fmt.Println("test_total", targetResult)
  134. return targetResult
  135. }

port.go

  1. package collector
  2. import (
  3. "sync"
  4. "github.com/prometheus/client_golang/prometheus"
  5. "github.com/shirou/gopsutil/host"
  6. )
  7. var (
  8. isexist float64 = 1
  9. namespace = "own_process"
  10. endetail = "datails"
  11. endmems = "mems"
  12. )
  13. // 定义收集指标结构体
  14. // 分为进程信息和内存信息
  15. type PortCollector struct {
  16. ProcessDetail portMetrics
  17. ProcessMems portMetrics
  18. mutex sync.Mutex // 使用于多个协程访问共享资源的场景
  19. // value prometheus.Gauge
  20. }
  21. type portMetrics []struct {
  22. desc *prometheus.Desc
  23. value map[string]string
  24. }
  25. func (p *PortCollector) Describe(ch chan<- *prometheus.Desc) {
  26. for _, metric := range p.ProcessDetail {
  27. ch <- metric.desc
  28. }
  29. for _, metric := range p.ProcessMems {
  30. ch <- metric.desc
  31. }
  32. // ch <- p.ProcessMems
  33. }
  34. func (p *PortCollector) Collect(ch chan<- prometheus.Metric) {
  35. p.mutex.Lock()
  36. defer p.mutex.Unlock()
  37. // ch <- prometheus.MustNewConstMetric(p.ProcessMems, prometheus.GaugeValue, 0)
  38. for _, metric := range p.ProcessDetail {
  39. ch <- prometheus.MustNewConstMetric(metric.desc, prometheus.GaugeValue, isexist, metric.value["cmdroot"], metric.value["cmdline"], metric.value["Name"], metric.value["State"], metric.value["PPid"], metric.value["Uid"], metric.value["Gid"])
  40. }
  41. for _, metric := range p.ProcessMems {
  42. ch <- prometheus.MustNewConstMetric(metric.desc, prometheus.GaugeValue, isexist, metric.value["Name"], metric.value["pid"], metric.value["VmHWM"], metric.value["VmRSS"])
  43. }
  44. }
  45. func (p *PortCollector) Updata() {
  46. // Do nothing here as the value is generated in the Collect() function
  47. }
  48. func newMetrics(p []string, s map[string]map[string]string, u string) *portMetrics {
  49. host, _ := host.Info()
  50. hostname := host.Hostname
  51. var detailList, memsList portMetrics
  52. for _, v := range p {
  53. // fmt.Println(k, v)
  54. detailList = append(detailList, struct {
  55. desc *prometheus.Desc
  56. value map[string]string
  57. }{
  58. desc: prometheus.NewDesc(
  59. prometheus.BuildFQName(namespace, v, endetail),
  60. "Process-related information of port "+v,
  61. []string{"cmdroot", "cmdline", "process_name", "status", "ppid", "ownuser", "owngroup"}, // 设置动态labels,collect函数里传来的就是这个变量的值
  62. prometheus.Labels{"host_name": hostname}), // 设置静态labels
  63. value: s[v],
  64. })
  65. memsList = append(memsList, struct {
  66. desc *prometheus.Desc
  67. value map[string]string
  68. }{
  69. desc: prometheus.NewDesc(
  70. prometheus.BuildFQName(namespace, v, endmems),
  71. "Process memory usage information of port "+v,
  72. []string{"process_name", "pid", "vmhwm", "vmrss"}, // 设置动态labels,collect函数里传来的就是这个变量的值
  73. prometheus.Labels{"host_name": hostname}), // 设置静态labels
  74. value: s[v],
  75. })
  76. }
  77. if u == "detail" {
  78. return &detailList
  79. } else {
  80. return &memsList
  81. }
  82. }
  83. // NewPortCollector 创建port收集器,返回指标信息
  84. func NewPortCollector(p []string, m string) *PortCollector {
  85. final := GetProcessInfo(p, m)
  86. // fmt.Printf("test_fanal:%#v", len(final))
  87. if len(final) == 0 {
  88. isexist = 0
  89. } else {
  90. isexist = 1
  91. }
  92. return &PortCollector{
  93. ProcessDetail: *newMetrics(p, final, "detail"),
  94. ProcessMems: *newMetrics(p, final, "mems"),
  95. }
  96. }

以上就是如何使用go自定义prometheus的exporter的详细内容,更多关于如何使用go自定义prometheus的exporter的资料请关注九品源码其它相关文章!