文章目录
- 1. 安装client_golang库
- 2. 编写可观测监控代码
- 3. 运行效果
- 4. jar、graalvm、golang编译运行版本对比
 
前文使用java+graalvm实现原生应用可观测监控: prometheus client_java实现进程的CPU、内存、IO、流量的可观测,但是部分java依赖包使用了复杂的反射功能,Graalvm编译可能失败,无法在运行过程中获取反射类,需要手动添加反射类,比较麻烦,容易出现编译原生失败的情况。
这里介绍基于Prometheus的client_golang库实现应用可观测监控,使用Go语言实现,编译更简单。
官方教程:[https://prometheus.io/docs/guides/go-application/](Instrumenting a Go application for Prometheus)
client_golang: https://github.com/prometheus/client_golang
1. 安装client_golang库
当前最新版本v1.20.5
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promauto
go get github.com/prometheus/client_golang/prometheus/promhttp
2. 编写可观测监控代码
实现进程的CPU、内存、IO、流量的可观测监控,部分实现代码如下:
package metrics
import (
	"bufio"
	"container/list"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/prometheus/client_golang/prometheus"
	"os/exec"
	"task-exporter/common"
	"task-exporter/models"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)
type TopMetrics struct{}
var Registry = prometheus.NewRegistry()
var upGauge = prometheus.NewGauge(prometheus.GaugeOpts{
	Name: METRICS_UP_USE,
	Help: "help",
})
var processCpuGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{
	Name: METRICS_CPU_USE,
	Help: "help",
}, []string{LABEL_PID, LABEL_USER, LABEL_CMD, LABEL_INCLUDE})
var processMemGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{
	Name: METRICS_MEM_USE,
	Help: "help",
}, []string{LABEL_PID, LABEL_USER, LABEL_CMD, LABEL_INCLUDE})
var processResGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{
	Name: METRICS_RES_USE,
	Help: "help",
}, []string{LABEL_PID, LABEL_USER, LABEL_CMD, LABEL_INCLUDE})
func init() {
	upGauge.Set(1)
	Registry.MustRegister(upGauge)
	Registry.MustRegister(processCpuGauge)
	Registry.MustRegister(processMemGauge)
	Registry.MustRegister(processResGauge)
	fmt.Println("=====init=====")
}
func (top *TopMetrics) Run() {
	topData, _ := top.handle()
	processList := topData.ProcessList
	var elements []models.ProcessInfo
	for e := processList.Front(); e != nil; e = e.Next() {
		elements = append(elements, e.Value.(models.ProcessInfo))
	}
	elements = top.topCpuMem(elements, "cpu")
	processCpuGauge.Reset()
	for row, v := range elements {
		if row < common.ConfigInfo.Limit {
			processCpuGauge.WithLabelValues(v.Pid, v.User, v.Cmd, v.In).Set(v.Cpu)
		}
	}
	// MEM,RES
	elements = top.topCpuMem(elements, "res")
	processMemGauge.Reset()
	processResGauge.Reset()
	for row, v := range elements {
		if row < common.ConfigInfo.Limit {
			processMemGauge.WithLabelValues(v.Pid, v.User, v.Cmd, v.In).Set(v.Mem)
			processResGauge.WithLabelValues(v.Pid, v.User, v.Cmd, v.In).Set(v.Res)
		}
	}
}
func (top *TopMetrics) handle() (models.TopData, error) {
	var topData = models.TopData{ProcessList: list.New()}
	// 创建一个 Command 对象,指定要执行的外部命令及其参数
	cmd := exec.Command("top", "-c", "-b", "-n", "1", "-w", "512")
	// 获取命令的标准输出
	stdout, err := cmd.StdoutPipe()
	defer stdout.Close()
	if err != nil {
		fmt.Println("Error creating StdoutPipe:", err)
		return topData, errors.New("Error creating StdoutPipe:")
	}
	// 启动命令
	if err := cmd.Start(); err != nil {
		fmt.Println("Error starting command:", err)
		return topData, errors.New("Error starting command")
	}
	// 使用 bufio.Scanner 逐行读取命令的输出
	scanner := bufio.NewScanner(stdout)
	//fmt.Println("\n=====start=====")
	row := 1
	for scanner.Scan() {
		line := scanner.Text()
		//fmt.Println(row, "======:", line)
		if row == 1 {
			//top.handleUptime(line, row, &topData)
		} else if row == 2 {
			//top.handleTask(line, row, &topData)
		} else if row == 3 {
			//top.handleCpu(line, row, &topData)
		} else if row == 4 {
			//top.handleMem(line, row, &topData)
		} else if row == 5 {
			//top.handleSwap(line, row, &topData)
		} else if row >= 8 {
			top.handleProcess(line, row, &topData)
		}
		row++
	}
	// 等待命令执行完成
	if err := cmd.Wait(); err != nil {
		fmt.Println("Error waiting for top command to finish:", err)
		return topData, errors.New("exec: not started")
	}
	// 检查扫描过程中是否有错误
	if err := scanner.Err(); err != nil {
		fmt.Println("Error reading output:", err)
	}
	return topData, nil
}
func (top *TopMetrics) handleProcess(line string, row int, topData *models.TopData) {
	//                                       pid      user      PR       NI    VIRT      RES     SHR     S        %CPU        %MEM         TIME    COMMAND
	processRegex := regexp.MustCompile("(\\d+)\\s+(\\S+)\\s+\\S+\\s+\\S+\\s+\\S+\\s+(\\S+)\\s+\\S+\\s+\\S+\\s+([\\d.]+)\\s+([\\d.]+)\\s+\\S+\\s(.+)")
	processMatches := processRegex.FindStringSubmatch(line)
	if len(processMatches) == 7 {
		process := models.ProcessInfo{In: "n"}
		process.Pid = processMatches[1]
		process.User = processMatches[2]
		res := processMatches[3]
		if strings.HasSuffix(res, "m") {
			process.Res, _ = strconv.ParseFloat(strings.Trim(res, "m"), 64)
			process.Res = process.Res * 1024
		} else if strings.HasSuffix(res, "g") {
			process.Res, _ = strconv.ParseFloat(strings.Trim(res, "g"), 64)
			process.Res = process.Res * 1024 * 1024
		} else {
			process.Res, _ = strconv.ParseFloat(res, 64)
		}
		process.Cpu, _ = strconv.ParseFloat(processMatches[4], 64)
		process.Mem, _ = strconv.ParseFloat(processMatches[5], 64)
		process.Cmd = strings.TrimSpace(processMatches[6])
		// 检查别名
		process.Cmd = common.FilterAlias(process.Cmd)
		topData.ProcessList.PushBack(process)
	} else {
		strB, _ := json.Marshal(processMatches)
		fmt.Println(row, "======processRegex found:", string(strB))
	}
}
/**
 * 采集进程信息CPU\MEM\RES
 */
func (top *TopMetrics) topCpuMem(processes []models.ProcessInfo, ptype string) []models.ProcessInfo {
	len := len(processes)
	//fmt.Println("======topCpuMem len:", len)
	for i := 0; i < len-1; i++ {
		for r := i + 1; r < len; r++ {
			if ptype == "cpu" {
				if processes[r].Cpu > processes[i].Cpu {
					processes[i], processes[r] = processes[r], processes[i]
				}
			} else if ptype == "mem" {
				if processes[r].Mem > processes[i].Mem {
					processes[i], processes[r] = processes[r], processes[i]
				}
			} else if ptype == "res" {
				if processes[r].Res > processes[i].Res {
					//fmt.Println("======res change:", i, processes[i], r, processes[r])
					processes[i], processes[r] = processes[r], processes[i]
				}
			}
		}
	}
	return processes
}
main.go
package main
import (
	"flag"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"log"
	"net/http"
	"task-exporter/metrics"
	"time"
)
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
func main() {
	registry := metrics.Registry
	go func() {
		for {
			top := metrics.TopMetrics{}
			top.Run()
			//io := metrics.IoMetrics{}
			//io.Run()
			//net := metrics.NethogsMetrics{}
			//net.Run()
			//port := metrics.NetstatMetrics{}
			//port.Run()
			time.Sleep(15 * time.Second)
		}
	}()
	http.Handle(
		"/metrics", promhttp.HandlerFor(
			registry,
			promhttp.HandlerOpts{
				EnableOpenMetrics: true,
			}),
	)
	log.Printf("Listening on http://localhost%s/metrics", *addr)
	log.Fatal(http.ListenAndServe(*addr, nil))
}
3. 运行效果

 Grafana展示效果参看前文。
4. jar、graalvm、golang编译运行版本对比
jar、graalvm、golang生成文件大小对比
 
 jar、graalvm、golang运行占用CPU、内存对比
 
go编译版本:task_exporter-linux-x86.zip














](https://i-blog.csdnimg.cn/direct/de4122e5c2d34143b803adb6d9a7292b.png)




