init: web_hgdjc_server
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
package healthser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"videoweb/app/api/proto"
|
||||
"videoweb/app/appg"
|
||||
"videoweb/pub/common/log"
|
||||
"videoweb/pub/common/netutil"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/mongos"
|
||||
"videoweb/pub/redis"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
timestampTolerance = 5 * time.Minute
|
||||
bytesInGB = 1024 * 1024 * 1024
|
||||
healthGroupID = "69840d508591cdac5a3defcb"
|
||||
healthSecretKey = "5e47c292e775ebe8bb0d79057cd17778"
|
||||
)
|
||||
|
||||
func HealthCheck(ctx context.Context, req *proto.HealthCheckRequest) (*proto.HealthCheckData, *srvdef.SrvErr) {
|
||||
if req.GroupID != healthGroupID {
|
||||
log.Warn("health check invalid groupId", log.Any("groupId", req.GroupID))
|
||||
return nil, srvdef.New(srvdef.ErrParamError, "invalid groupId")
|
||||
}
|
||||
if !isTimestampValid(req.Timestamp) {
|
||||
log.Warn("health check timestamp expired", log.Any("timestamp", req.Timestamp))
|
||||
return nil, srvdef.New(srvdef.ErrParamError, "timestamp expired")
|
||||
}
|
||||
if !verifySign(req.GroupID, req.Timestamp, healthSecretKey, req.Sign) {
|
||||
log.Warn("health check invalid sign", log.Any("groupId", req.GroupID))
|
||||
return nil, srvdef.New(srvdef.ErrParamError, "invalid sign")
|
||||
}
|
||||
|
||||
data := collectServerMetrics()
|
||||
data.Timestamp = time.Now().Unix()
|
||||
data.Mysql = disabledStatus("mysql not used")
|
||||
data.Kafka = disabledStatus("kafka not used")
|
||||
data.Redis = pingRedis()
|
||||
data.Mongodb = pingMongo(ctx)
|
||||
data.Elasticsearch = pingElasticsearch(ctx)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func verifySign(groupID string, ts int64, secret string, sign string) bool {
|
||||
payload := fmt.Sprintf("%s:%d", groupID, ts)
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write([]byte(payload))
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
return hmac.Equal([]byte(strings.ToLower(sign)), []byte(expected))
|
||||
}
|
||||
|
||||
func isTimestampValid(ts int64) bool {
|
||||
now := time.Now().Unix()
|
||||
diff := now - ts
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
return diff <= int64(timestampTolerance.Seconds())
|
||||
}
|
||||
|
||||
func disabledStatus(msg string) *proto.ComponentStatus {
|
||||
return &proto.ComponentStatus{Status: "disabled", Msg: msg}
|
||||
}
|
||||
|
||||
func okStatus() *proto.ComponentStatus {
|
||||
return &proto.ComponentStatus{Status: "ok", Msg: "success"}
|
||||
}
|
||||
|
||||
func pingRedis() *proto.ComponentStatus {
|
||||
cli := redis.Client()
|
||||
if cli == nil {
|
||||
return &proto.ComponentStatus{Status: "err", Msg: "redis client not initialized"}
|
||||
}
|
||||
if err := cli.Ping().Err(); err != nil {
|
||||
log.Warn("health check redis ping failed", log.E(err))
|
||||
return &proto.ComponentStatus{Status: "err", Msg: err.Error()}
|
||||
}
|
||||
status := okStatus()
|
||||
status.Master = appg.Conf.Redis.Addr
|
||||
return status
|
||||
}
|
||||
|
||||
func pingMongo(ctx context.Context) *proto.ComponentStatus {
|
||||
if mongos.Shop == nil {
|
||||
return &proto.ComponentStatus{Status: "err", Msg: "mongo client not initialized"}
|
||||
}
|
||||
pingCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
if err := mongos.Shop.Database().RunCommand(pingCtx, bson.D{{Key: "ping", Value: 1}}).Err(); err != nil {
|
||||
log.Warn("health check mongo ping failed", log.E(err))
|
||||
return &proto.ComponentStatus{Status: "err", Msg: err.Error()}
|
||||
}
|
||||
return okStatus()
|
||||
}
|
||||
|
||||
func pingElasticsearch(ctx context.Context) *proto.ComponentStatus {
|
||||
addr := strings.TrimSpace(appg.Conf.Es.Addr)
|
||||
if addr == "" {
|
||||
return disabledStatus("elasticsearch not configured")
|
||||
}
|
||||
url := strings.TrimSuffix(addr, "/") + "/_cluster/health"
|
||||
reqCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
log.Warn("health check es request build failed", log.E(err))
|
||||
return &proto.ComponentStatus{Status: "err", Msg: err.Error()}
|
||||
}
|
||||
if appg.Conf.Es.User != "" || appg.Conf.Es.Pass != "" {
|
||||
req.SetBasicAuth(appg.Conf.Es.User, appg.Conf.Es.Pass)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Warn("health check es request failed", log.E(err))
|
||||
return &proto.ComponentStatus{Status: "err", Msg: err.Error()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return &proto.ComponentStatus{Status: "err", Msg: fmt.Sprintf("es status %d", resp.StatusCode)}
|
||||
}
|
||||
return okStatus()
|
||||
}
|
||||
|
||||
func collectServerMetrics() *proto.HealthCheckData {
|
||||
name, err := os.Hostname()
|
||||
if err != nil || name == "" {
|
||||
name = "app-server"
|
||||
}
|
||||
ip := netutil.GetLocalIP()
|
||||
if ip == "" {
|
||||
ip = "127.0.0.1"
|
||||
}
|
||||
|
||||
cores := runtime.NumCPU()
|
||||
usedCores := calcUsedCores(cores)
|
||||
memMetrics := memoryMetrics()
|
||||
diskMetrics := diskMetrics()
|
||||
|
||||
return &proto.HealthCheckData{
|
||||
ServerName: name,
|
||||
ServerIP: ip,
|
||||
CPU: proto.HealthCPUMetrics{
|
||||
Cores: cores,
|
||||
Used: usedCores,
|
||||
},
|
||||
Memory: memMetrics,
|
||||
Disk: diskMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
func calcUsedCores(cores int) float64 {
|
||||
usage, err := cpu.Percent(200*time.Millisecond, false)
|
||||
if err != nil || len(usage) == 0 {
|
||||
if err != nil {
|
||||
log.Warn("health check cpu percent failed", log.E(err))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
used := float64(cores) * usage[0] / 100
|
||||
return math.Round(used*10) / 10
|
||||
}
|
||||
|
||||
func memoryMetrics() proto.HealthMemoryMetrics {
|
||||
vm, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
log.Warn("health check memory stats failed", log.E(err))
|
||||
return proto.HealthMemoryMetrics{}
|
||||
}
|
||||
return proto.HealthMemoryMetrics{
|
||||
Total: bytesToGB(vm.Total),
|
||||
Used: bytesToGB(vm.Used),
|
||||
}
|
||||
}
|
||||
|
||||
func diskMetrics() proto.HealthMemoryMetrics {
|
||||
path := rootPath()
|
||||
du, err := disk.Usage(path)
|
||||
if err != nil {
|
||||
log.Warn("health check disk stats failed", log.E(err))
|
||||
return proto.HealthMemoryMetrics{}
|
||||
}
|
||||
return proto.HealthMemoryMetrics{
|
||||
Total: bytesToGB(du.Total),
|
||||
Used: bytesToGB(du.Used),
|
||||
}
|
||||
}
|
||||
|
||||
func bytesToGB(value uint64) int64 {
|
||||
if value == 0 {
|
||||
return 0
|
||||
}
|
||||
return int64(math.Round(float64(value) / bytesInGB))
|
||||
}
|
||||
|
||||
func rootPath() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "C:\\"
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
Reference in New Issue
Block a user