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,193 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var metricNameRE = regexp.MustCompile(`[^a-zA-Z0-9_:]`)
|
||||
var labelNameRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
|
||||
type prometheusSample struct {
|
||||
name string
|
||||
typ Type
|
||||
labels string
|
||||
value int64
|
||||
float bool
|
||||
floatValue float64
|
||||
}
|
||||
|
||||
func Handler(registry *Registry) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
||||
ObserveRuntime(registry)
|
||||
registry.RunObservers()
|
||||
WritePrometheus(w, registry.Snapshot())
|
||||
}
|
||||
}
|
||||
|
||||
func WritePrometheus(w interface{ Write([]byte) (int, error) }, samples []Sample) {
|
||||
seenTypes := make(map[string]Type)
|
||||
for _, sample := range normalizeSamples(samples) {
|
||||
// Histogram 多个子样本共享同一 base 名字 (<base>_bucket / _count / _sum),
|
||||
// TYPE 标注必须按 family 去重;其他类型按完整 metric 名去重保持原行为。
|
||||
typeKey := sample.name
|
||||
if sample.typ == TypeHistogram {
|
||||
typeKey = histogramFamily(sample.name)
|
||||
}
|
||||
if existing, ok := seenTypes[typeKey]; ok {
|
||||
if existing != sample.typ {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
seenTypes[typeKey] = sample.typ
|
||||
_, _ = fmt.Fprintf(w, "# TYPE %s %s\n", typeKey, prometheusType(sample.typ))
|
||||
}
|
||||
if sample.float {
|
||||
// 用 %g 而非 %f:histogram 的 sum 既要避免 1.0e-06 这种科学计数,
|
||||
// 又要避免拖一长串无意义的零;%g 在大多数 latency 场景下输出可读小数。
|
||||
_, _ = fmt.Fprintf(w, "%s%s %g\n", sample.name, sample.labels, sample.floatValue)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(w, "%s%s %d\n", sample.name, sample.labels, sample.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// histogramFamily 把 <base>_bucket / <base>_count / <base>_sum
|
||||
// 还原成 <base>,用于 TYPE 标注去重。未识别后缀时返回原名,等价于"该样本自己就是 family"。
|
||||
func histogramFamily(name string) string {
|
||||
for _, suffix := range []string{"_sum", "_count", "_bucket"} {
|
||||
if strings.HasSuffix(name, suffix) {
|
||||
return strings.TrimSuffix(name, suffix)
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func normalizeSamples(samples []Sample) []prometheusSample {
|
||||
merged := make(map[string]prometheusSample, len(samples))
|
||||
for _, sample := range samples {
|
||||
name := normalizeName(sample.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
labels := formatLabels(sample.Labels)
|
||||
key := string(sample.Type) + "\x00" + name + "\x00" + labels
|
||||
current := merged[key]
|
||||
if current.name == "" {
|
||||
current = prometheusSample{name: name, typ: sample.Type, labels: labels}
|
||||
}
|
||||
// Histogram observers 写入的是绝对值(替换语义,跟 gauge 一致),不能累加;
|
||||
// 否则 bucket / count / sum 会被多次叠加。Gauge 同理。
|
||||
switch sample.Type {
|
||||
case TypeGauge, TypeHistogram:
|
||||
current.value = sample.Value
|
||||
current.float = sample.Float
|
||||
current.floatValue = sample.FloatValue
|
||||
default:
|
||||
current.value += sample.Value
|
||||
}
|
||||
merged[key] = current
|
||||
}
|
||||
out := make([]prometheusSample, 0, len(merged))
|
||||
for _, sample := range merged {
|
||||
out = append(out, sample)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if out[i].name != out[j].name {
|
||||
return out[i].name < out[j].name
|
||||
}
|
||||
if out[i].typ != out[j].typ {
|
||||
return out[i].typ < out[j].typ
|
||||
}
|
||||
return out[i].labels < out[j].labels
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func prometheusType(t Type) string {
|
||||
switch t {
|
||||
case TypeGauge:
|
||||
return "gauge"
|
||||
case TypeHistogram:
|
||||
return "histogram"
|
||||
default:
|
||||
return "counter"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeName(name string) string {
|
||||
return normalizeIdentifier(name, metricNameRE, true)
|
||||
}
|
||||
|
||||
func normalizeLabelName(name string) string {
|
||||
return normalizeIdentifier(name, labelNameRE, false)
|
||||
}
|
||||
|
||||
func normalizeIdentifier(name string, invalid *regexp.Regexp, allowColonFirst bool) string {
|
||||
name = strings.TrimSpace(name)
|
||||
name = invalid.ReplaceAllString(name, "_")
|
||||
name = strings.Trim(name, "_")
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
first := name[0]
|
||||
if isAlpha(first) || first == '_' || (allowColonFirst && first == ':') {
|
||||
return name
|
||||
}
|
||||
return "_" + name
|
||||
}
|
||||
|
||||
func isAlpha(b byte) bool {
|
||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
|
||||
}
|
||||
|
||||
func formatLabels(labels map[string]string) string {
|
||||
if len(labels) == 0 {
|
||||
return ""
|
||||
}
|
||||
normalized := make(map[string]string, len(labels))
|
||||
keys := make([]string, 0, len(labels))
|
||||
originalKeys := make([]string, 0, len(labels))
|
||||
for key := range labels {
|
||||
originalKeys = append(originalKeys, key)
|
||||
}
|
||||
sort.Strings(originalKeys)
|
||||
for _, key := range originalKeys {
|
||||
name := normalizeLabelName(key)
|
||||
if name != "" {
|
||||
if _, exists := normalized[name]; exists {
|
||||
continue
|
||||
}
|
||||
normalized[name] = labels[key]
|
||||
keys = append(keys, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
if len(keys) == 0 {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteByte('{')
|
||||
for idx, key := range keys {
|
||||
if idx > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
b.WriteString(key)
|
||||
b.WriteString(`="`)
|
||||
b.WriteString(escapeLabel(normalized[key]))
|
||||
b.WriteByte('"')
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func escapeLabel(value string) string {
|
||||
value = strings.ReplaceAll(value, `\`, `\\`)
|
||||
value = strings.ReplaceAll(value, "\n", `\n`)
|
||||
value = strings.ReplaceAll(value, `"`, `\"`)
|
||||
return value
|
||||
}
|
||||
Reference in new issue
Block a user