mirror of
https://github.com/TeaOSLab/EdgeCommon.git
synced 2026-04-08 08:25:21 +08:00
阶段性提交
This commit is contained in:
64
pkg/serverconfigs/metric_item_config.go
Normal file
64
pkg/serverconfigs/metric_item_config.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package serverconfigs
|
||||
|
||||
import (
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// MetricItemCategory 指标分类
|
||||
type MetricItemCategory = string
|
||||
|
||||
const (
|
||||
MetricItemCategoryHTTP MetricItemCategory = "http"
|
||||
MetricItemCategoryTCP MetricItemCategory = "tcp"
|
||||
MetricItemCategoryUDP MetricItemCategory = "udp"
|
||||
)
|
||||
|
||||
// MetricItemPeriodUnit 指标周期单位
|
||||
type MetricItemPeriodUnit = string
|
||||
|
||||
const (
|
||||
MetricItemPeriodUnitMinute MetricItemPeriodUnit = "minute"
|
||||
MetricItemPeriodUnitHour MetricItemPeriodUnit = "hour"
|
||||
MetricItemPeriodUnitDay MetricItemPeriodUnit = "day"
|
||||
MetricItemPeriodUnitWeek MetricItemPeriodUnit = "week"
|
||||
MetricItemPeriodUnitMonth MetricItemPeriodUnit = "month"
|
||||
)
|
||||
|
||||
// MetricItemConfig 指标配置
|
||||
type MetricItemConfig struct {
|
||||
Id int64 `yaml:"id" json:"id"`
|
||||
IsOn bool `yaml:"isOn" json:"isOn"`
|
||||
Category MetricItemCategory `yaml:"category" json:"category"`
|
||||
Period int `yaml:"period" json:"period"`
|
||||
PeriodUnit MetricItemPeriodUnit `yaml:"periodUnit" json:"periodUnit"`
|
||||
Keys []string `yaml:"keys" json:"keys"`
|
||||
Value string `yaml:"value" json:"value"`
|
||||
|
||||
sumType string // 统计类型
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (this *MetricItemConfig) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseRequest 处理请求
|
||||
func (this *MetricItemConfig) ParseRequest(format func(string) string) (key string, hash string, value float64) {
|
||||
for _, k := range this.Keys {
|
||||
key += "@" + format(k)
|
||||
}
|
||||
hash = strconv.FormatUint(xxhash.Sum64String(key), 10)
|
||||
|
||||
// TODO value将来支持复杂运算,比如 ${request.traffic.bytes} * 8
|
||||
if len(this.Value) == 0 {
|
||||
value = 1
|
||||
} else {
|
||||
value = types.Float64(format(this.Value))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
39
pkg/serverconfigs/metric_item_config_test.go
Normal file
39
pkg/serverconfigs/metric_item_config_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package serverconfigs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMetricItemConfig_ProcessRequest(t *testing.T) {
|
||||
var metric = &MetricItemConfig{
|
||||
Keys: []string{"${remoteAddr}", "${status}", "${requestPath}"},
|
||||
Value: "${trafficIn}",
|
||||
}
|
||||
key, hash, value := metric.ParseRequest(func(s string) string {
|
||||
return configutils.ParseVariables(s, func(varName string) (value string) {
|
||||
switch varName {
|
||||
case "trafficIn":
|
||||
return "1000"
|
||||
}
|
||||
return "[" + varName + "]"
|
||||
})
|
||||
})
|
||||
t.Log("key:", key, "hash:", hash)
|
||||
t.Logf("value: %f", value)
|
||||
}
|
||||
|
||||
func BenchmarkMetricItemConfig_ProcessRequest(b *testing.B) {
|
||||
var metric = &MetricItemConfig{
|
||||
Keys: []string{"${remoteAddr}", "${status}"},
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
metric.ParseRequest(func(s string) string {
|
||||
return configutils.ParseVariables(s, func(varName string) (value string) {
|
||||
return "[" + varName + "]"
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
249
pkg/serverconfigs/vars/var_func.go
Normal file
249
pkg/serverconfigs/vars/var_func.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package vars
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// func列表,code => type
|
||||
var funcMap = map[string]reflect.Value{
|
||||
"float": reflect.ValueOf(FuncFloat),
|
||||
"round": reflect.ValueOf(FuncRound),
|
||||
"ceil": reflect.ValueOf(FuncCeil),
|
||||
"floor": reflect.ValueOf(FuncFloor),
|
||||
"format": reflect.ValueOf(FuncFormat),
|
||||
"append": reflect.ValueOf(FuncAppend),
|
||||
}
|
||||
|
||||
// FuncFloat 浮点数处理
|
||||
// float | float('%.2f')
|
||||
func FuncFloat(args ...interface{}) interface{} {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(args) == 1 {
|
||||
return types.Float64(args[0])
|
||||
}
|
||||
args[0] = types.Float64(args[0])
|
||||
return FuncFormat(args...)
|
||||
}
|
||||
|
||||
// FuncRound 对数字四舍五入
|
||||
// round | round(2)
|
||||
func FuncRound(args ...interface{}) interface{} {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(args) == 1 {
|
||||
return int64(math.Round(types.Float64(args[0])))
|
||||
}
|
||||
|
||||
precision := types.Int64(args[1])
|
||||
if precision <= 0 {
|
||||
return int64(math.Round(types.Float64(args[0])))
|
||||
}
|
||||
return fmt.Sprintf("%."+strconv.FormatInt(precision, 10)+"f", types.Float64(args[0]))
|
||||
}
|
||||
|
||||
// FuncCeil 对数字进行取不小于它的整数
|
||||
// ceil
|
||||
func FuncCeil(args ...interface{}) interface{} {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
return int64(math.Ceil(types.Float64(args[0])))
|
||||
}
|
||||
|
||||
// FuncFloor 对数字进行取不大于它的整数
|
||||
// floor
|
||||
func FuncFloor(args ...interface{}) interface{} {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
return int64(math.Floor(types.Float64(args[0])))
|
||||
}
|
||||
|
||||
// FuncFormat 格式化
|
||||
// format('%.2f')
|
||||
func FuncFormat(args ...interface{}) interface{} {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(args) == 1 {
|
||||
return types.String(args[0])
|
||||
}
|
||||
format := types.String(args[1])
|
||||
if len(args) == 2 {
|
||||
return fmt.Sprintf(format, args[0])
|
||||
} else {
|
||||
return fmt.Sprintf(format, append([]interface{}{args[0]}, args[2:]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// FuncAppend 附加字符串
|
||||
// append('a', 'b')
|
||||
func FuncAppend(args ...interface{}) interface{} {
|
||||
s := strings.Builder{}
|
||||
for _, arg := range args {
|
||||
s.WriteString(types.String(arg))
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// RunFuncExpr 执行函数表达式
|
||||
func RunFuncExpr(value interface{}, expr []byte) (interface{}, error) {
|
||||
// 防止因nil参数导致panic
|
||||
if value == nil {
|
||||
value = ""
|
||||
}
|
||||
|
||||
// 空表达式直接返回
|
||||
if len(expr) == 0 || len(bytes.TrimSpace(expr)) == 0 {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
identifier := []byte{}
|
||||
|
||||
isInQuote := false
|
||||
isQuoted := false
|
||||
quoteByte := byte(0)
|
||||
funcCode := ""
|
||||
args := []interface{}{value}
|
||||
|
||||
for index, b := range expr {
|
||||
switch b {
|
||||
case '|':
|
||||
if isInQuote {
|
||||
identifier = append(identifier, b)
|
||||
} else { // end function
|
||||
if len(funcCode) == 0 {
|
||||
funcCode = string(identifier)
|
||||
} else if len(identifier) > 0 {
|
||||
return value, errors.New("invalid identifier '" + string(identifier) + "'")
|
||||
}
|
||||
value, err := callFunc(funcCode, args)
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return RunFuncExpr(value, expr[index+1:])
|
||||
}
|
||||
case '(':
|
||||
if isInQuote {
|
||||
identifier = append(identifier, b)
|
||||
} else {
|
||||
funcCode = string(identifier)
|
||||
identifier = []byte{}
|
||||
}
|
||||
case ',', ')':
|
||||
if isInQuote {
|
||||
identifier = append(identifier, b)
|
||||
} else {
|
||||
if isQuoted {
|
||||
isQuoted = false
|
||||
args = append(args, string(identifier))
|
||||
} else {
|
||||
arg, err := checkLiteral(string(identifier))
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
args = append(args, arg)
|
||||
}
|
||||
identifier = []byte{}
|
||||
}
|
||||
case '\\':
|
||||
if isInQuote && (index == len(expr)-1 || expr[index+1] != quoteByte) {
|
||||
identifier = append(identifier, b)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case '\'', '"':
|
||||
if isInQuote {
|
||||
if quoteByte == b && expr[index-1] != '\\' {
|
||||
isInQuote = false
|
||||
break
|
||||
}
|
||||
} else if index == 0 || expr[index-1] != '\\' {
|
||||
isInQuote = true
|
||||
isQuoted = true
|
||||
quoteByte = b
|
||||
break
|
||||
}
|
||||
identifier = append(identifier, b)
|
||||
case ' ', '\t':
|
||||
if isInQuote {
|
||||
identifier = append(identifier, b)
|
||||
}
|
||||
default:
|
||||
identifier = append(identifier, b)
|
||||
}
|
||||
}
|
||||
|
||||
if len(funcCode) == 0 {
|
||||
funcCode = string(identifier)
|
||||
} else if len(identifier) > 0 {
|
||||
return value, errors.New("invalid identifier '" + string(identifier) + "'")
|
||||
}
|
||||
|
||||
return callFunc(funcCode, args)
|
||||
}
|
||||
|
||||
// RegisterFunc 注册一个函数
|
||||
func RegisterFunc(code string, f interface{}) {
|
||||
funcMap[code] = reflect.ValueOf(f)
|
||||
}
|
||||
|
||||
// 调用一个函数
|
||||
func callFunc(funcCode string, args []interface{}) (value interface{}, err error) {
|
||||
fn, ok := funcMap[funcCode]
|
||||
if !ok {
|
||||
return value, errors.New("function '" + funcCode + "' not found")
|
||||
}
|
||||
argValues := []reflect.Value{}
|
||||
for _, arg := range args {
|
||||
argValues = append(argValues, reflect.ValueOf(arg))
|
||||
}
|
||||
result := fn.Call(argValues)
|
||||
if len(result) == 0 {
|
||||
value = nil
|
||||
} else {
|
||||
value = result[0].Interface()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查字面量,支持true, false, null, nil和整数数字、浮点数数字(不支持e)
|
||||
func checkLiteral(identifier string) (result interface{}, err error) {
|
||||
if len(identifier) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
switch strings.ToLower(identifier) {
|
||||
case "true":
|
||||
result = true
|
||||
return
|
||||
case "false":
|
||||
result = false
|
||||
return
|
||||
case "null", "nil":
|
||||
result = nil
|
||||
return
|
||||
default:
|
||||
if shared.RegexpAllDigitNumber.MatchString(identifier) {
|
||||
result = types.Int64(identifier)
|
||||
return
|
||||
}
|
||||
if shared.RegexpAllFloatNumber.MatchString(identifier) {
|
||||
result = types.Float64(identifier)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.New("undefined identifier '" + identifier + "'")
|
||||
return
|
||||
}
|
||||
188
pkg/serverconfigs/vars/var_func_test.go
Normal file
188
pkg/serverconfigs/vars/var_func_test.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package vars
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncFloat(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
a.IsTrue(FuncFloat() == 0)
|
||||
a.IsTrue(FuncFloat("123.456789") == 123.456789)
|
||||
a.IsTrue(FuncFloat("123.456789", "%.2f") == "123.46")
|
||||
}
|
||||
|
||||
func TestFuncFormat(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
t.Log(FuncFormat(123.456789, "%.2f"))
|
||||
a.IsTrue(FuncFormat(123.456789, "%.2f") == "123.46")
|
||||
a.IsTrue(FuncFormat(123.456123, "%.3f%s", "HELLO") == "123.456HELLO")
|
||||
}
|
||||
|
||||
func TestFuncAppend(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
a.IsTrue(FuncAppend("a", "b", "c") == "abc")
|
||||
}
|
||||
|
||||
func TestFuncCall(t *testing.T) {
|
||||
{
|
||||
funcType := reflect.ValueOf(FuncFloat)
|
||||
values := funcType.Call([]reflect.Value{reflect.ValueOf("123.4567890123")})
|
||||
for _, v := range values {
|
||||
t.Log(v.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
funcType := reflect.ValueOf(FuncAppend)
|
||||
values := funcType.Call([]reflect.Value{reflect.ValueOf("a"), reflect.ValueOf("b"), reflect.ValueOf("c")})
|
||||
for _, v := range values {
|
||||
t.Log(v.Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFuncExpr(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
t.Log(RunFuncExpr(123.456789, []byte("float|format('%.3f%s,%s', 'a', 'b')|append('a1','b2','c3')")))
|
||||
t.Log(RunFuncExpr(123.456, []byte("append('a', 'b2', 'c345\"', \"6789'10'\", '\\'Hello\\'\\\"')")))
|
||||
t.Log(RunFuncExpr(123.456, []byte("append('78910')|float|format('%.6f')")))
|
||||
t.Log(RunFuncExpr(123.456, []byte("format('%.2f') | append('a', 'b', 'c\td')")))
|
||||
t.Log(RunFuncExpr(123.456, []byte("append(123.456, true, 'a')")))
|
||||
t.Log(RunFuncExpr(123.456, []byte("")))
|
||||
t.Log(RunFuncExpr(123.456, []byte(" ")))
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr(123.456, []byte(" format('%.2f')"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.IsTrue(v == "123.46")
|
||||
}
|
||||
|
||||
t.Log(RunFuncExpr(nil, []byte("float")))
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr("123.456", []byte("round"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(v)
|
||||
a.IsTrue(v == int64(123))
|
||||
}
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr("123.567", []byte("round"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(v)
|
||||
a.IsTrue(v == int64(124))
|
||||
}
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr("123.567", []byte("round(2)"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(v)
|
||||
a.IsTrue(v == "123.57")
|
||||
}
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr("123.4567123", []byte("round(4)"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(v)
|
||||
a.IsTrue(v == "123.4567")
|
||||
}
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr("123.567", []byte("ceil"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(v)
|
||||
a.IsTrue(v == int64(124))
|
||||
}
|
||||
|
||||
{
|
||||
v, err := RunFuncExpr("123.567", []byte("floor"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(v)
|
||||
a.IsTrue(v == int64(123))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLiteral(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
{
|
||||
_, err := checkLiteral("abc")
|
||||
a.IsNotNil(err)
|
||||
t.Log(err)
|
||||
}
|
||||
{
|
||||
result, err := checkLiteral("true")
|
||||
a.IsNil(err)
|
||||
a.IsTrue(result == true)
|
||||
}
|
||||
{
|
||||
result, err := checkLiteral("false")
|
||||
a.IsNil(err)
|
||||
a.IsTrue(result == false)
|
||||
}
|
||||
|
||||
{
|
||||
result, err := checkLiteral("null")
|
||||
a.IsNil(err)
|
||||
a.IsTrue(result == nil)
|
||||
}
|
||||
|
||||
{
|
||||
result, err := checkLiteral("nil")
|
||||
a.IsNil(err)
|
||||
a.IsTrue(result == nil)
|
||||
}
|
||||
|
||||
{
|
||||
result, err := checkLiteral("123")
|
||||
a.IsNil(err)
|
||||
t.Log(result)
|
||||
a.IsTrue(result == int64(123))
|
||||
}
|
||||
|
||||
{
|
||||
result, err := checkLiteral("+123")
|
||||
a.IsNil(err)
|
||||
t.Log(result)
|
||||
a.IsTrue(result == int64(123))
|
||||
}
|
||||
|
||||
{
|
||||
result, err := checkLiteral("-123")
|
||||
a.IsNil(err)
|
||||
t.Log(result)
|
||||
a.IsTrue(result == int64(-123))
|
||||
}
|
||||
|
||||
{
|
||||
result, err := checkLiteral("123.456")
|
||||
a.IsNil(err)
|
||||
t.Log(result)
|
||||
a.IsTrue(result == 123.456)
|
||||
}
|
||||
{
|
||||
result, err := checkLiteral("-123.456")
|
||||
a.IsNil(err)
|
||||
t.Log(result)
|
||||
a.IsTrue(result == -123.456)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user