DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,669 @@
|
||||
package topology
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"longheng.io/server/internal/platform/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
RoleGate = "gate"
|
||||
RoleNode = "node"
|
||||
RoleMesh = "mesh"
|
||||
|
||||
LinkGatewayBackend = "gateway_backend"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
Services []string
|
||||
GatewayService string
|
||||
GatewayBackendService string
|
||||
RejectMaintaining bool
|
||||
RefreshInterval time.Duration
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
RefreshIntervalSeconds int64 `json:"refresh_interval_seconds"`
|
||||
Services []ServiceSnapshot `json:"services"`
|
||||
Gates []NodeSnapshot `json:"gates"`
|
||||
Nodes []NodeSnapshot `json:"nodes"`
|
||||
Mesh MeshSnapshot `json:"mesh"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceSnapshot struct {
|
||||
Service string `json:"service"`
|
||||
Role string `json:"role"`
|
||||
TotalNodes int `json:"total_nodes"`
|
||||
ReadyNodes int `json:"ready_nodes"`
|
||||
MaintainingNodes int `json:"maintaining_nodes"`
|
||||
Routes int `json:"routes"`
|
||||
Events int `json:"events"`
|
||||
MeshServices int `json:"mesh_services"`
|
||||
}
|
||||
|
||||
type NodeSnapshot struct {
|
||||
Service string `json:"service"`
|
||||
NodeID string `json:"node_id"`
|
||||
Role string `json:"role"`
|
||||
ClusterID string `json:"cluster_id,omitempty"`
|
||||
GID int64 `json:"gid,omitempty"`
|
||||
ShardID int64 `json:"shard_id,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Zone string `json:"zone,omitempty"`
|
||||
PublicAddr string `json:"public_addr,omitempty"`
|
||||
PrivateAddr string `json:"private_addr,omitempty"`
|
||||
AdminAddr string `json:"admin_addr,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
DataVersion string `json:"data_version,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
Maintaining bool `json:"maintaining"`
|
||||
Accepting bool `json:"accepting"`
|
||||
Routes []RouteSnapshot `json:"routes,omitempty"`
|
||||
Events []EventSnapshot `json:"events,omitempty"`
|
||||
Services []string `json:"services,omitempty"`
|
||||
Observability *ObservabilitySnapshot `json:"observability,omitempty"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
RegisteredAt time.Time `json:"registered_at"`
|
||||
}
|
||||
|
||||
type ObservabilitySnapshot struct {
|
||||
MetricsEnabled bool `json:"metrics_enabled"`
|
||||
MetricsPath string `json:"metrics_path,omitempty"`
|
||||
MetricsURL string `json:"metrics_url,omitempty"`
|
||||
MetricsSnapshotPath string `json:"metrics_snapshot_path,omitempty"`
|
||||
MetricsSnapshotURL string `json:"metrics_snapshot_url,omitempty"`
|
||||
MetricsRequireAdminToken bool `json:"metrics_require_admin_token,omitempty"`
|
||||
PprofEnabled bool `json:"pprof_enabled"`
|
||||
PprofPath string `json:"pprof_path,omitempty"`
|
||||
PprofURL string `json:"pprof_url,omitempty"`
|
||||
}
|
||||
|
||||
type RouteSnapshot struct {
|
||||
ID string `json:"id"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Internal bool `json:"internal,omitempty"`
|
||||
Stateful bool `json:"stateful,omitempty"`
|
||||
Authorized bool `json:"authorized,omitempty"`
|
||||
Idempotent bool `json:"idempotent,omitempty"`
|
||||
}
|
||||
|
||||
type EventSnapshot struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type MeshSnapshot struct {
|
||||
Services []string `json:"services"`
|
||||
Links []LinkSnapshot `json:"links"`
|
||||
}
|
||||
|
||||
type LinkSnapshot struct {
|
||||
Kind string `json:"kind"`
|
||||
SourceService string `json:"source_service"`
|
||||
TargetService string `json:"target_service"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
registry registry.Registry
|
||||
logger *slog.Logger
|
||||
|
||||
mu sync.RWMutex
|
||||
cfg Config
|
||||
snapshot Snapshot
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
generation uint64
|
||||
}
|
||||
|
||||
var detachedRootContext = context.Background()
|
||||
|
||||
func New(cfg Config, reg registry.Registry, logger *slog.Logger) *Manager {
|
||||
cfg = normalizeConfig(cfg)
|
||||
return &Manager{
|
||||
registry: reg,
|
||||
logger: logger,
|
||||
cfg: cfg,
|
||||
snapshot: Snapshot{
|
||||
Enabled: cfg.Enabled,
|
||||
RefreshIntervalSeconds: int64(cfg.RefreshInterval / time.Second),
|
||||
Mesh: MeshSnapshot{Services: append([]string(nil), cfg.Services...)},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Name() string {
|
||||
return "topology-manager"
|
||||
}
|
||||
|
||||
func (m *Manager) Start(ctx context.Context) error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.RLock()
|
||||
generation := m.generation
|
||||
m.mu.RUnlock()
|
||||
return m.start(ctx, generation)
|
||||
}
|
||||
|
||||
func (m *Manager) start(ctx context.Context, generation uint64) error {
|
||||
m.mu.RLock()
|
||||
enabled := m.cfg.Enabled
|
||||
started := m.cancel != nil
|
||||
interval := m.cfg.RefreshInterval
|
||||
current := m.generation
|
||||
m.mu.RUnlock()
|
||||
if generation != current || started || !enabled {
|
||||
return nil
|
||||
}
|
||||
if err := m.Refresh(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
if m.generation != generation || m.cancel != nil || !m.cfg.Enabled {
|
||||
m.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
runCtx, cancel := context.WithCancel(detachedContext(ctx))
|
||||
done := make(chan struct{})
|
||||
m.cancel = cancel
|
||||
m.done = done
|
||||
m.mu.Unlock()
|
||||
|
||||
go m.loop(runCtx, done, interval)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) AfterStart(ctx context.Context) error {
|
||||
if m == nil || !m.Enabled() {
|
||||
return nil
|
||||
}
|
||||
return m.Refresh(ctx)
|
||||
}
|
||||
|
||||
func (m *Manager) Stop(ctx context.Context) error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
m.mu.Lock()
|
||||
cancel := m.cancel
|
||||
done := m.done
|
||||
m.generation++
|
||||
m.cancel = nil
|
||||
m.done = nil
|
||||
m.mu.Unlock()
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
if done == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Configure(cfg Config) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
cfg = normalizeConfig(cfg)
|
||||
m.mu.Lock()
|
||||
wasEnabled := m.cfg.Enabled
|
||||
intervalChanged := m.cfg.RefreshInterval != cfg.RefreshInterval
|
||||
cancel := m.cancel
|
||||
done := m.done
|
||||
m.generation++
|
||||
generation := m.generation
|
||||
m.cfg = cfg
|
||||
m.snapshot.Enabled = cfg.Enabled
|
||||
m.snapshot.RefreshIntervalSeconds = int64(cfg.RefreshInterval / time.Second)
|
||||
if wasEnabled && (!cfg.Enabled || intervalChanged) {
|
||||
m.cancel = nil
|
||||
m.done = nil
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
if wasEnabled && (!cfg.Enabled || intervalChanged) && cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
if cfg.Enabled && (!wasEnabled || intervalChanged) {
|
||||
if done != nil {
|
||||
go m.startAfterDone(done, generation)
|
||||
return
|
||||
}
|
||||
_ = m.start(detachedRootContext, generation)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) startAfterDone(done <-chan struct{}, generation uint64) {
|
||||
<-done
|
||||
_ = m.start(detachedRootContext, generation)
|
||||
}
|
||||
|
||||
func detachedContext(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
return detachedRootContext
|
||||
}
|
||||
return context.WithoutCancel(ctx)
|
||||
}
|
||||
|
||||
func (m *Manager) Enabled() bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
m.mu.RLock()
|
||||
enabled := m.cfg.Enabled
|
||||
m.mu.RUnlock()
|
||||
return enabled
|
||||
}
|
||||
|
||||
func (m *Manager) Refresh(ctx context.Context) error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
m.mu.RLock()
|
||||
cfg := m.cfg
|
||||
reg := m.registry
|
||||
m.mu.RUnlock()
|
||||
if !cfg.Enabled {
|
||||
m.storeSnapshot(buildSnapshot(cfg, nil, nil))
|
||||
return nil
|
||||
}
|
||||
if reg == nil {
|
||||
snapshot := buildSnapshot(cfg, nil, nil)
|
||||
snapshot.Error = "registry is required"
|
||||
m.storeSnapshot(snapshot)
|
||||
return registry.ErrClosed
|
||||
}
|
||||
|
||||
nodesByService := make(map[string][]registry.Node, len(cfg.Services))
|
||||
var firstErr error
|
||||
for _, service := range cfg.Services {
|
||||
nodes, err := reg.List(ctx, service)
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
nodesByService[service] = nodes
|
||||
}
|
||||
snapshot := buildSnapshot(cfg, nodesByService, firstErr)
|
||||
m.storeSnapshot(snapshot)
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (m *Manager) Snapshot() Snapshot {
|
||||
if m == nil {
|
||||
return Snapshot{}
|
||||
}
|
||||
m.mu.RLock()
|
||||
snapshot := cloneSnapshot(m.snapshot)
|
||||
m.mu.RUnlock()
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func (m *Manager) loop(ctx context.Context, done chan struct{}, interval time.Duration) {
|
||||
defer close(done)
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := m.Refresh(ctx); err != nil && m.logger != nil {
|
||||
m.logger.Warn("topology refresh failed", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) storeSnapshot(snapshot Snapshot) {
|
||||
m.mu.Lock()
|
||||
m.snapshot = snapshot
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func buildSnapshot(cfg Config, nodesByService map[string][]registry.Node, err error) Snapshot {
|
||||
snapshot := Snapshot{
|
||||
Enabled: cfg.Enabled,
|
||||
RefreshIntervalSeconds: int64(cfg.RefreshInterval / time.Second),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Mesh: MeshSnapshot{
|
||||
Services: append([]string(nil), cfg.Services...),
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
snapshot.Error = err.Error()
|
||||
}
|
||||
serviceReady := make(map[string]bool, len(cfg.Services))
|
||||
for _, service := range cfg.Services {
|
||||
role := roleForService(cfg, service)
|
||||
nodes := nodesByService[service]
|
||||
serviceSnapshot := ServiceSnapshot{Service: service, Role: role}
|
||||
for _, node := range nodes {
|
||||
nodeSnapshot := nodeFromRegistry(cfg, role, node)
|
||||
serviceSnapshot.TotalNodes++
|
||||
serviceSnapshot.Routes += len(nodeSnapshot.Routes)
|
||||
serviceSnapshot.Events += len(nodeSnapshot.Events)
|
||||
serviceSnapshot.MeshServices += len(nodeSnapshot.Services)
|
||||
if nodeSnapshot.Maintaining {
|
||||
serviceSnapshot.MaintainingNodes++
|
||||
}
|
||||
if nodeSnapshot.Accepting {
|
||||
serviceSnapshot.ReadyNodes++
|
||||
serviceReady[service] = true
|
||||
}
|
||||
if role == RoleGate {
|
||||
snapshot.Gates = append(snapshot.Gates, nodeSnapshot)
|
||||
} else {
|
||||
snapshot.Nodes = append(snapshot.Nodes, nodeSnapshot)
|
||||
}
|
||||
}
|
||||
snapshot.Services = append(snapshot.Services, serviceSnapshot)
|
||||
}
|
||||
if cfg.GatewayService != "" && cfg.GatewayBackendService != "" {
|
||||
snapshot.Mesh.Links = append(snapshot.Mesh.Links, LinkSnapshot{
|
||||
Kind: LinkGatewayBackend,
|
||||
SourceService: cfg.GatewayService,
|
||||
TargetService: cfg.GatewayBackendService,
|
||||
Available: serviceReady[cfg.GatewayService] && serviceReady[cfg.GatewayBackendService],
|
||||
})
|
||||
}
|
||||
sortSnapshot(&snapshot)
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func nodeFromRegistry(cfg Config, role string, node registry.Node) NodeSnapshot {
|
||||
meta := cloneMeta(node.Meta)
|
||||
maintaining := parseBool(meta["maintaining"]) || parseBool(meta["is_maintaining"]) || parseBool(meta["maintenance"])
|
||||
state := firstNonEmpty(node.State, meta["state"])
|
||||
accepting := state != "stopping" && state != "stopped" && (!cfg.RejectMaintaining || !maintaining)
|
||||
return NodeSnapshot{
|
||||
Service: strings.TrimSpace(node.Service),
|
||||
NodeID: strings.TrimSpace(node.NodeID),
|
||||
Role: role,
|
||||
ClusterID: strings.TrimSpace(meta["cluster_id"]),
|
||||
GID: parseInt64(meta["gid"]),
|
||||
ShardID: parseInt64(meta["shard_id"]),
|
||||
Environment: firstNonEmpty(meta["environment"], meta["env"]),
|
||||
Zone: strings.TrimSpace(meta["zone"]),
|
||||
PublicAddr: firstNonEmpty(node.PublicAddr, meta["public_addr"]),
|
||||
PrivateAddr: firstNonEmpty(node.PrivateAddr, meta["private_addr"]),
|
||||
AdminAddr: strings.TrimSpace(meta["admin_addr"]),
|
||||
Version: strings.TrimSpace(meta["version"]),
|
||||
DataVersion: strings.TrimSpace(meta["data_version"]),
|
||||
State: state,
|
||||
Weight: nodeWeight(node),
|
||||
Maintaining: maintaining,
|
||||
Accepting: accepting,
|
||||
Routes: routesFromRegistry(node.Routes),
|
||||
Events: eventsFromRegistry(node.Events),
|
||||
Services: cloneStrings(node.Services),
|
||||
Observability: obsFromMeta(meta),
|
||||
Meta: meta,
|
||||
RegisteredAt: node.RegisteredAt,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeConfig(cfg Config) Config {
|
||||
cfg.Services = normalizeServices(cfg.Services)
|
||||
cfg.GatewayService = strings.TrimSpace(cfg.GatewayService)
|
||||
cfg.GatewayBackendService = strings.TrimSpace(cfg.GatewayBackendService)
|
||||
if cfg.RefreshInterval <= 0 {
|
||||
cfg.RefreshInterval = 5 * time.Second
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func normalizeServices(services []string) []string {
|
||||
seen := make(map[string]struct{}, len(services))
|
||||
out := make([]string, 0, len(services))
|
||||
for _, service := range services {
|
||||
service = strings.TrimSpace(service)
|
||||
if service == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[service]; ok {
|
||||
continue
|
||||
}
|
||||
seen[service] = struct{}{}
|
||||
out = append(out, service)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func roleForService(cfg Config, service string) string {
|
||||
if strings.TrimSpace(service) == cfg.GatewayService {
|
||||
return RoleGate
|
||||
}
|
||||
if cfg.GatewayBackendService != "" && strings.TrimSpace(service) != cfg.GatewayBackendService {
|
||||
return RoleMesh
|
||||
}
|
||||
return RoleNode
|
||||
}
|
||||
|
||||
func sortSnapshot(snapshot *Snapshot) {
|
||||
sort.Slice(snapshot.Services, func(i, j int) bool {
|
||||
return snapshot.Services[i].Service < snapshot.Services[j].Service
|
||||
})
|
||||
sort.Slice(snapshot.Gates, func(i, j int) bool {
|
||||
return snapshot.Gates[i].NodeID < snapshot.Gates[j].NodeID
|
||||
})
|
||||
sort.Slice(snapshot.Nodes, func(i, j int) bool {
|
||||
if snapshot.Nodes[i].Service != snapshot.Nodes[j].Service {
|
||||
return snapshot.Nodes[i].Service < snapshot.Nodes[j].Service
|
||||
}
|
||||
return snapshot.Nodes[i].NodeID < snapshot.Nodes[j].NodeID
|
||||
})
|
||||
sort.Slice(snapshot.Mesh.Links, func(i, j int) bool {
|
||||
if snapshot.Mesh.Links[i].SourceService != snapshot.Mesh.Links[j].SourceService {
|
||||
return snapshot.Mesh.Links[i].SourceService < snapshot.Mesh.Links[j].SourceService
|
||||
}
|
||||
return snapshot.Mesh.Links[i].TargetService < snapshot.Mesh.Links[j].TargetService
|
||||
})
|
||||
}
|
||||
|
||||
func cloneSnapshot(snapshot Snapshot) Snapshot {
|
||||
snapshot.Services = append([]ServiceSnapshot(nil), snapshot.Services...)
|
||||
snapshot.Gates = append([]NodeSnapshot(nil), snapshot.Gates...)
|
||||
for idx := range snapshot.Gates {
|
||||
snapshot.Gates[idx].Meta = cloneMeta(snapshot.Gates[idx].Meta)
|
||||
snapshot.Gates[idx].Routes = append([]RouteSnapshot(nil), snapshot.Gates[idx].Routes...)
|
||||
snapshot.Gates[idx].Events = append([]EventSnapshot(nil), snapshot.Gates[idx].Events...)
|
||||
snapshot.Gates[idx].Services = cloneStrings(snapshot.Gates[idx].Services)
|
||||
snapshot.Gates[idx].Observability = cloneObservability(snapshot.Gates[idx].Observability)
|
||||
}
|
||||
snapshot.Nodes = append([]NodeSnapshot(nil), snapshot.Nodes...)
|
||||
for idx := range snapshot.Nodes {
|
||||
snapshot.Nodes[idx].Meta = cloneMeta(snapshot.Nodes[idx].Meta)
|
||||
snapshot.Nodes[idx].Routes = append([]RouteSnapshot(nil), snapshot.Nodes[idx].Routes...)
|
||||
snapshot.Nodes[idx].Events = append([]EventSnapshot(nil), snapshot.Nodes[idx].Events...)
|
||||
snapshot.Nodes[idx].Services = cloneStrings(snapshot.Nodes[idx].Services)
|
||||
snapshot.Nodes[idx].Observability = cloneObservability(snapshot.Nodes[idx].Observability)
|
||||
}
|
||||
snapshot.Mesh.Services = append([]string(nil), snapshot.Mesh.Services...)
|
||||
snapshot.Mesh.Links = append([]LinkSnapshot(nil), snapshot.Mesh.Links...)
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func cloneMeta(meta map[string]string) map[string]string {
|
||||
if len(meta) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(meta))
|
||||
for key, value := range meta {
|
||||
out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneObservability(observability *ObservabilitySnapshot) *ObservabilitySnapshot {
|
||||
if observability == nil {
|
||||
return nil
|
||||
}
|
||||
out := *observability
|
||||
return &out
|
||||
}
|
||||
|
||||
func obsFromMeta(meta map[string]string) *ObservabilitySnapshot {
|
||||
metricsEnabled := parseBool(meta["metrics_enabled"])
|
||||
pprofEnabled := parseBool(meta["pprof_enabled"])
|
||||
metricsPath := ""
|
||||
if metricsEnabled {
|
||||
metricsPath = firstNonEmpty(meta["metrics_path"], "/metrics")
|
||||
}
|
||||
metricsSnapPath := strings.TrimSpace(meta["metrics_snapshot_path"])
|
||||
if metricsSnapPath == "" && meta["metrics_enabled"] != "" {
|
||||
metricsSnapPath = "/debug/metrics"
|
||||
}
|
||||
pprofPath := ""
|
||||
if pprofEnabled {
|
||||
pprofPath = firstNonEmpty(meta["pprof_path"], "/debug/pprof/")
|
||||
}
|
||||
if !metricsEnabled && !pprofEnabled && metricsSnapPath == "" {
|
||||
return nil
|
||||
}
|
||||
adminAddr := strings.TrimSpace(meta["admin_addr"])
|
||||
return &ObservabilitySnapshot{
|
||||
MetricsEnabled: metricsEnabled,
|
||||
MetricsPath: metricsPath,
|
||||
MetricsURL: endpointURL(adminAddr, metricsPath),
|
||||
MetricsSnapshotPath: metricsSnapPath,
|
||||
MetricsSnapshotURL: endpointURL(adminAddr, metricsSnapPath),
|
||||
MetricsRequireAdminToken: parseBool(meta["metrics_require_admin_token"]),
|
||||
PprofEnabled: pprofEnabled,
|
||||
PprofPath: pprofPath,
|
||||
PprofURL: endpointURL(adminAddr, pprofPath),
|
||||
}
|
||||
}
|
||||
|
||||
func endpointURL(addr, path string) string {
|
||||
addr = strings.TrimSpace(addr)
|
||||
path = strings.TrimSpace(path)
|
||||
if addr == "" || path == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
if strings.HasPrefix(addr, "http://") || strings.HasPrefix(addr, "https://") {
|
||||
return strings.TrimRight(addr, "/") + path
|
||||
}
|
||||
if strings.HasPrefix(addr, ":") {
|
||||
return "http://127.0.0.1" + addr + path
|
||||
}
|
||||
return "http://" + addr + path
|
||||
}
|
||||
|
||||
func parseBool(value string) bool {
|
||||
switch strings.TrimSpace(strings.ToLower(value)) {
|
||||
case "1", "true", "yes", "y", "on":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseInt64(value string) int64 {
|
||||
parsed, _ := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
||||
return parsed
|
||||
}
|
||||
|
||||
func parseInt(value string) int {
|
||||
parsed, _ := strconv.Atoi(strings.TrimSpace(value))
|
||||
return parsed
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func nodeWeight(node registry.Node) int {
|
||||
if node.Weight > 0 {
|
||||
return node.Weight
|
||||
}
|
||||
weight := parseInt(node.Meta["weight"])
|
||||
if weight > 0 {
|
||||
return weight
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func routesFromRegistry(routes []registry.Route) []RouteSnapshot {
|
||||
if len(routes) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]RouteSnapshot, 0, len(routes))
|
||||
for _, route := range routes {
|
||||
id := strings.TrimSpace(route.ID)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, RouteSnapshot{
|
||||
ID: id,
|
||||
Group: strings.TrimSpace(route.Group),
|
||||
Internal: route.Internal,
|
||||
Stateful: route.Stateful,
|
||||
Authorized: route.Authorized,
|
||||
Idempotent: route.Idempotent,
|
||||
})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
return out[i].ID < out[j].ID
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func eventsFromRegistry(events []registry.Event) []EventSnapshot {
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]EventSnapshot, 0, len(events))
|
||||
for _, event := range events {
|
||||
name := strings.TrimSpace(event.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, EventSnapshot{Name: name})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
return out[i].Name < out[j].Name
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneStrings(values []string) []string {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := append([]string(nil), values...)
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user