diff --git a/reader/cloudwatch/cloudwatch.go b/reader/cloudwatch/cloudwatch.go index d38945293..95a5b4cbe 100644 --- a/reader/cloudwatch/cloudwatch.go +++ b/reader/cloudwatch/cloudwatch.go @@ -430,7 +430,7 @@ func formatKey(metricName string, statistic string) string { } func snakeCase(s string) string { - s = models.PandoraKey(s) + s, _ = models.PandoraKey(s) s = strings.Replace(s, "__", "_", -1) return s } diff --git a/utils/models/utils.go b/utils/models/utils.go index 4729443e0..a1bbd4d26 100644 --- a/utils/models/utils.go +++ b/utils/models/utils.go @@ -752,31 +752,74 @@ func GetMapList(data string) map[string]string { return ret } -func PandoraKey(key string) string { - var nk string - for _, c := range key { +// 判断时只有数字和字母为合法字符,规则: +// 1. 首字符为数字时,增加首字符 "K" +// 2. 首字符为非法字符时,去掉首字符(例如,如果字符串全为非法字符,则转换后为空) +// 3. 非首字符并且为非法字符时,使用 "_" 替代非法字符 +func PandoraKey(key string) (string, bool) { + // check + valid := true + size := 0 + for idx, c := range key { if c >= '0' && c <= '9' { - if len(nk) == 0 { - nk = "K" + size++ + if idx == 0 { + size++ + valid = false } - nk = nk + string(c) - } else if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { - nk = nk + string(c) - } else if len(nk) > 0 { - nk = nk + "_" + continue + } + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { + size++ + continue + } + + if idx > 0 && size > 0 { + size++ } + valid = false } - return nk + if valid { + return key, true + } + + if size <= 0 { + return "", false + } + + // set + bytes := make([]byte, size) + bp := 0 + for idx, c := range key { + if c >= '0' && c <= '9' { + if idx == 0 { + bp += copy(bytes, "K") + } + bp += copy(bytes[bp:], string(c)) + continue + } + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { + bp += copy(bytes[bp:], string(c)) + continue + } + + if idx > 0 { + bp += copy(bytes[bp:], "_") + } + } + return string(bytes), false } func DeepConvertKey(data map[string]interface{}) map[string]interface{} { - newData := make(map[string]interface{}) for k, v := range data { - nk := PandoraKey(k) + nk, valid := PandoraKey(k) if nv, ok := v.(map[string]interface{}); ok { v = DeepConvertKey(nv) } - newData[nk] = v + if !valid { + delete(data, k) + data[nk] = v + } } - return newData + return data } diff --git a/utils/models/utils_test.go b/utils/models/utils_test.go index bb7f70039..b22e2b321 100644 --- a/utils/models/utils_test.go +++ b/utils/models/utils_test.go @@ -618,9 +618,44 @@ func TestPickMapValue(t *testing.T) { func TestPandoraKey(t *testing.T) { testKeys := []string{"@timestamp", ".dot", "percent%100", "^^^^^^^^^^", "timestamp"} expectKeys := []string{"timestamp", "dot", "percent_100", "", "timestamp"} + expectValid := []bool{false, false, false, false, true} for idx, key := range testKeys { - actual := PandoraKey(key) + actual, valid := PandoraKey(key) assert.Equal(t, expectKeys[idx], actual) + assert.Equal(t, expectValid[idx], valid) + } +} + +func BenchmarkPandoraKey(b *testing.B) { + b.ReportAllocs() + testKeys := []string{"@timestamp", ".dot", "percent%100", "^^^^^^^^^^", "timestamp", "aaa"} + for i := 0; i < b.N; i++ { + for _, key := range testKeys { + PandoraKey(key) + } + } +} + +func BenchmarkDeepConvertKey(b *testing.B) { + b.ReportAllocs() + testDatas := []map[string]interface{}{ + { + "@timestamp": "2018-07-18T10:17:36.549054846+08:00", + //"timestamp": "2018-07-19T10:17:36.549054846+08:00", + }, + { + ".dot": map[string]interface{}{".dot2": "dot"}, + }, + { + "dot": map[string]interface{}{".dot2": "dot"}, + "percent%100": 100, + "^^^^^^^^^^": "mytest", + }, + } + for i := 0; i < b.N; i++ { + for _, data := range testDatas { + DeepConvertKey(data) + } } } @@ -631,10 +666,10 @@ func TestDeepConvertKey(t *testing.T) { //"timestamp": "2018-07-19T10:17:36.549054846+08:00", }, { - ".dot": "dot", + ".dot": map[string]interface{}{".dot2": "dot"}, }, { - "dot": "dot", + "dot": map[string]interface{}{".dot2": "dot"}, "percent%100": 100, "^^^^^^^^^^": "mytest", }, @@ -644,10 +679,10 @@ func TestDeepConvertKey(t *testing.T) { "timestamp": "2018-07-18T10:17:36.549054846+08:00", }, { - "dot": "dot", + "dot": map[string]interface{}{"dot2": "dot"}, }, { - "dot": "dot", + "dot": map[string]interface{}{"dot2": "dot"}, "percent_100": 100, "": "mytest", },