You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
4.6 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package app
import (
"bytes"
"context"
"joybuywork/internal/consts"
"encoding/json"
"fmt"
"net"
"runtime"
"runtime/pprof"
"time"
)
var Info ServiceInfo
func SetUpCollector() {
Info = ServiceInfo{
Name: consts.ServiceName,
NodeId: fmt.Sprint(NodeId),
GoVersion: runtime.Version(),
StartAt: time.Now().Unix(),
LastReportAt: time.Now().Unix(),
Ip: GetLocalIP(),
}
}
type PProfCollector struct {
}
// 初始化采集器
func NewPProfCollector() *PProfCollector {
return &PProfCollector{}
}
// 采集运行时指标
func (c *PProfCollector) collectRuntimeMetrics() map[string]any {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
return map[string]any{
"timestamp": time.Now().Unix(),
"heap_alloc": memStats.HeapAlloc, // 当前堆内存分配(字节)
"heap_sys": memStats.HeapSys, // 堆内存总申请量
"goroutines": runtime.NumGoroutine(), // Goroutine 数量
"gc_count": memStats.NumGC, // GC 次数
"gc_pause_ns": memStats.PauseTotalNs, // GC 总暂停时间(纳秒)
}
}
// 采集 Heap Profile 并存储到 Redis
func (c *PProfCollector) saveHeapProfile() {
var buf bytes.Buffer
if err := pprof.WriteHeapProfile(&buf); err != nil {
Logger.Error(context.Background(), fmt.Sprintf("Failed to collect heap profile: %v", err))
return
}
// 存储为 Redis String(二进制数据)
key := fmt.Sprintf("joybuywork:app:heap:%v:%s", NodeId, time.Now().Format("20060102_1504"))
if err := Redis.Cache.Set(context.Background(), key, buf.Bytes(), 30*time.Minute).Err(); err != nil {
Logger.Error(context.Background(), fmt.Sprintf("Failed to save heap profile to Redis: %v", err))
} else {
Logger.Info(context.Background(), fmt.Sprintf("Heap profile saved: %s", key))
}
}
// 采集 CPU Profile(需谨慎,仅在必要时启用)
func (c *PProfCollector) saveCPUProfile(duration time.Duration) {
var buf bytes.Buffer
if err := pprof.StartCPUProfile(&buf); err != nil {
Logger.Error(context.Background(), fmt.Sprintf("Failed to start CPU profile: %v", err))
return
}
time.Sleep(duration)
pprof.StopCPUProfile()
key := fmt.Sprintf("joybuywork:app:cpu:%v:%s", NodeId, time.Now().Format("20060102_1504"))
if err := Redis.Cache.Set(context.Background(), key, buf.Bytes(), 30*time.Minute).Err(); err != nil {
Logger.Error(context.Background(), fmt.Sprintf("Failed to save CPU profile to Redis: %v", err))
} else {
Logger.Info(context.Background(), fmt.Sprintf("CPU profile saved: %s", key))
}
}
// 存储运行时指标到 Redis(JSON 格式)
func (c *PProfCollector) saveRuntimeMetrics() {
ctx := context.Background()
// 指标存储
metrics := c.collectRuntimeMetrics()
jsonData, err := json.Marshal(metrics)
if err != nil {
Logger.Error(ctx, fmt.Sprintf("Failed to marshal metrics: %v", err))
return
}
key := fmt.Sprintf("joybuywork:app:metrics:%v:%s", NodeId, time.Now().Format("20060102"))
if err := Redis.Cache.HSet(context.Background(), key, time.Now().Format("1504"), jsonData).Err(); err != nil {
Logger.Error(context.Background(), fmt.Sprintf("Failed to save metrics to Redis: %v", err))
} else {
Redis.Cache.Expire(ctx, key, 24*time.Hour)
Logger.Info(context.Background(), fmt.Sprintf("Metrics saved: %s", key))
}
}
// 启动定时采集任务
func (c *PProfCollector) Start() {
c.saveRuntimeMetrics()
c.saveHeapProfile()
c.saveCPUProfile(30 * time.Second) // 可选:采集 10 秒 CPU 数据
}
type ServiceInfo struct {
Name string `json:"name"` //服务名称
NodeId string `json:"node_id"` //节点id
GoVersion string `json:"go_version"` //GO版本
StartAt int64 `json:"start_at"` //启动时间戳
LastReportAt int64 `json:"last_report_at"` //最后上报时间戳
Ip string `json:"ip"`
}
func HeartBeat() {
ctx := context.Background()
// 服务存储
Info.LastReportAt = time.Now().Unix()
jsonInfo, err := json.Marshal(Info)
if err != nil {
Logger.Error(ctx, fmt.Sprintf("Failed to collect service info to json: %v\n", err))
return
}
infokey := fmt.Sprintf("service_info:%s", Info.Name)
err = Redis.Cache.HSet(ctx, infokey, Info.NodeId, string(jsonInfo)).Err()
if err != nil {
Logger.Error(ctx, fmt.Sprintf("Failed to report service info to Redis: %v\n", err))
}
Redis.Cache.Expire(ctx, infokey, 1*time.Hour)
}
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
return ipNet.IP.String()
}
}
}
return ""
}