nightingale1/vos/metric.go

232 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vos
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"sync"
"github.com/didi/nightingale/v5/pkg/istr"
)
const (
SPLIT = "/"
)
type MetricPoint struct {
PK string `json:"pk"` // 内部字段ident、metric、sorted(tags)拼接之后算md5
Ident string `json:"ident"` // 资源标识,跟资源无关的监控数据,该字段为空
Alias string `json:"alias"` // 资源名称,跟资源无关的监控数据,该字段为空
Metric string `json:"metric"` // 监控指标名称
TagsMap map[string]string `json:"tags"` // 监控数据标签
TagsLst []string `json:"-"` // 内部字段用于对TagsMap排序
Time int64 `json:"time"` // 时间戳,单位是秒
ValueUntyped interface{} `json:"value"` // 监控数据数值可以是int float string但最终要能转换为float64
Value float64 `json:"-"` // 内部字段最终转换之后的float64数值
}
func (m *MetricPoint) Tidy(now int64) error {
if m == nil {
return fmt.Errorf("point is nil")
}
// 时间超前5分钟则报错
if m.Time-now > 300 {
return fmt.Errorf("point_time(%d) - server_time(%d) = %d. use ntp to calibrate host time?", m.Time, now, m.Time-now)
}
// 时间延迟30分钟则报错
if m.Time-now < -1800 {
return fmt.Errorf("point_time(%d) - server_time(%d) = %d. use ntp to calibrate host time?", m.Time, now, m.Time-now)
}
if m.Time <= 0 {
m.Time = now
}
if m.Metric == "" {
return fmt.Errorf("metric is blank")
}
if istr.SampleKeyInvalid(m.Metric) {
return fmt.Errorf("metric:%s contains reserved words", m.Metric)
}
if istr.SampleKeyInvalid(m.Ident) {
return fmt.Errorf("ident:%s contains reserved words", m.Ident)
}
if m.ValueUntyped == nil {
return fmt.Errorf("value is nil")
}
safemap := make(map[string]string)
for k, v := range m.TagsMap {
if istr.SampleKeyInvalid(k) {
return fmt.Errorf("tag key: %s contains reserved words", k)
}
if len(k) == 0 {
return fmt.Errorf("tag key is blank, metric: %s", m.Metric)
}
v = strings.Map(func(r rune) rune {
if r == '\t' ||
r == '\r' ||
r == '\n' ||
r == ',' ||
r == ' ' {
return '_'
}
return r
}, v)
if len(v) == 0 {
safemap[k] = "nil"
} else {
safemap[k] = v
}
}
m.TagsMap = safemap
valid := true
var vv float64
var err error
switch cv := m.ValueUntyped.(type) {
case string:
vv, err = strconv.ParseFloat(cv, 64)
if err != nil {
valid = false
}
case float64:
vv = cv
case uint64:
vv = float64(cv)
case int64:
vv = float64(cv)
case int:
vv = float64(cv)
default:
valid = false
}
if !valid {
return fmt.Errorf("value(%v) is illegal", m.Value)
}
m.Value = vv
return nil
}
// func DictedTagstring(s string) map[string]string {
// if i := strings.Index(s, " "); i != -1 {
// s = strings.Replace(s, " ", "", -1)
// }
// rmap := make(map[string]string)
// if s == "" {
// return rmap
// }
// tags := strings.Split(s, ",")
// for _, tag := range tags {
// pair := strings.SplitN(tag, "=", 2)
// if len(pair) != 2 {
// continue
// }
// if pair[0] == "" {
// continue
// }
// if pair[1] == "" {
// rmap[pair[0]] = "nil"
// } else {
// rmap[pair[0]] = pair[1]
// }
// }
// return rmap
// }
func DictedTagList(tags []string) map[string]string {
rmap := make(map[string]string)
if len(tags) == 0 {
return rmap
}
for _, tag := range tags {
pair := strings.SplitN(tag, "=", 2)
if len(pair) != 2 {
continue
}
if pair[0] == "" {
continue
}
if pair[1] == "" {
rmap[pair[0]] = "nil"
} else {
rmap[pair[0]] = pair[1]
}
}
return rmap
}
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func SortedTags(tags map[string]string) string {
if tags == nil {
return ""
}
size := len(tags)
if size == 0 {
return ""
}
ret := bufferPool.Get().(*bytes.Buffer)
ret.Reset()
defer bufferPool.Put(ret)
if size == 1 {
for k, v := range tags {
ret.WriteString(k)
ret.WriteString("=")
ret.WriteString(v)
}
return ret.String()
}
keys := make([]string, size)
i := 0
for k := range tags {
keys[i] = k
i++
}
sort.Strings(keys)
for j, key := range keys {
ret.WriteString(key)
ret.WriteString("=")
ret.WriteString(tags[key])
if j != size-1 {
ret.WriteString(",")
}
}
return ret.String()
}