diff --git a/server/ingester/flow_log/dbwriter/span_writer.go b/server/ingester/flow_log/dbwriter/span_writer.go index c6bb0925e4d..c6e5f875e61 100644 --- a/server/ingester/flow_log/dbwriter/span_writer.go +++ b/server/ingester/flow_log/dbwriter/span_writer.go @@ -108,6 +108,7 @@ func (t *SpanWithTraceID) Encode() { encoder.WriteString255(t.RequestResource) encoder.WriteString255(t.ResponseResult) encoder.WriteString255(t.BizProtocol) + encoder.WriteString255(t.BizResponseCode) encoder.WriteString255(t.ResponseException) if t.RequestId != nil { encoder.WriteVarintU64(*t.RequestId) diff --git a/server/ingester/flow_log/log_data/l7_flow_log.go b/server/ingester/flow_log/log_data/l7_flow_log.go index bfae3adc04c..86453e7a031 100644 --- a/server/ingester/flow_log/log_data/l7_flow_log.go +++ b/server/ingester/flow_log/log_data/l7_flow_log.go @@ -222,7 +222,7 @@ func L7FlowLogColumns() []*ckdb.Column { l7Columns = append(l7Columns, ckdb.NewColumn("_id", ckdb.UInt64)) l7Columns = append(l7Columns, L7BaseColumns()...) l7Columns = append(l7Columns, - ckdb.NewColumn("l7_protocol", ckdb.UInt8).SetIndex(ckdb.IndexNone).SetComment("0:未知 1:其他, 20:http1, 21:http2, 40:dubbo, 60:mysql, 80:redis, 100:kafka, 101:mqtt, 120:dns"), + ckdb.NewColumn("l7_protocol", ckdb.UInt8).SetIndex(ckdb.IndexNone).SetComment("0:未知, 20:http1, 21:http2, 40:dubbo, 60:mysql, 61:postgresql, 62:oracle, 63:dameng, 64:db2, 65:tdsql, 66:oceanbase, 67:goldendb, 68:kingbase, 80:redis, 100:kafka, 101:mqtt, 120:dns"), ckdb.NewColumn("biz_protocol", ckdb.LowCardinalityString).SetIndex(ckdb.IndexNone).SetComment("应用协议"), ckdb.NewColumn("version", ckdb.LowCardinalityString).SetComment("协议版本"), ckdb.NewColumn("type", ckdb.UInt8).SetIndex(ckdb.IndexNone).SetComment("日志类型, 0:请求, 1:响应, 2:会话"), diff --git a/server/libs/datatype/flow.go b/server/libs/datatype/flow.go index ce4d2c8f02c..0322e06fd1a 100644 --- a/server/libs/datatype/flow.go +++ b/server/libs/datatype/flow.go @@ -165,6 +165,11 @@ const ( L7_PROTOCOL_POSTGRE L7Protocol = 61 L7_PROTOCOL_ORACLE L7Protocol = 62 L7_PROTOCOL_DAMENG L7Protocol = 63 + L7_PROTOCOL_DB2 L7Protocol = 64 + L7_PROTOCOL_TDSQL L7Protocol = 65 + L7_PROTOCOL_OCEANBASE L7Protocol = 66 + L7_PROTOCOL_GOLDENDB L7Protocol = 67 + L7_PROTOCOL_KINGBASE L7Protocol = 68 L7_PROTOCOL_REDIS L7Protocol = 80 L7_PROTOCOL_MONGODB L7Protocol = 81 L7_PROTOCOL_MEMCACHED L7Protocol = 82 @@ -700,6 +705,36 @@ func (p L7Protocol) String(isTLS bool) string { } else { return "Dameng" } + case L7_PROTOCOL_DB2: + if isTLS { + return "DB2_TLS" + } else { + return "DB2" + } + case L7_PROTOCOL_TDSQL: + if isTLS { + return "TDSQL_TLS" + } else { + return "TDSQL" + } + case L7_PROTOCOL_OCEANBASE: + if isTLS { + return "OceanBase_TLS" + } else { + return "OceanBase" + } + case L7_PROTOCOL_GOLDENDB: + if isTLS { + return "GoldenDB_TLS" + } else { + return "GoldenDB" + } + case L7_PROTOCOL_KINGBASE: + if isTLS { + return "Kingbase_TLS" + } else { + return "Kingbase" + } case L7_PROTOCOL_ISO8583: if isTLS { return "ISO-8583_TLS" @@ -823,6 +858,11 @@ var L7ProtocolStringMap = map[string]L7Protocol{ strings.ToLower(L7_PROTOCOL_POSTGRE.String(false)): L7_PROTOCOL_POSTGRE, strings.ToLower(L7_PROTOCOL_ORACLE.String(false)): L7_PROTOCOL_ORACLE, strings.ToLower(L7_PROTOCOL_DAMENG.String(false)): L7_PROTOCOL_DAMENG, + strings.ToLower(L7_PROTOCOL_DB2.String(false)): L7_PROTOCOL_DB2, + strings.ToLower(L7_PROTOCOL_TDSQL.String(false)): L7_PROTOCOL_TDSQL, + strings.ToLower(L7_PROTOCOL_OCEANBASE.String(false)): L7_PROTOCOL_OCEANBASE, + strings.ToLower(L7_PROTOCOL_GOLDENDB.String(false)): L7_PROTOCOL_GOLDENDB, + strings.ToLower(L7_PROTOCOL_KINGBASE.String(false)): L7_PROTOCOL_KINGBASE, strings.ToLower(L7_PROTOCOL_ISO8583.String(false)): L7_PROTOCOL_ISO8583, strings.ToLower(L7_PROTOCOL_NET_SIGN.String(false)): L7_PROTOCOL_NET_SIGN, strings.ToLower(L7_PROTOCOL_TRIPLE.String(false)): L7_PROTOCOL_TRIPLE, diff --git a/server/libs/datatype/tag_test.go b/server/libs/datatype/tag_test.go index cb8c30d5e38..c1cfa343aa6 100644 --- a/server/libs/datatype/tag_test.go +++ b/server/libs/datatype/tag_test.go @@ -45,3 +45,25 @@ func TestTagEncodeAndDecode(t *testing.T) { t.Errorf("编解码函数实现错误") } } + +func TestCommercialDatabaseL7ProtocolMappings(t *testing.T) { + expected := map[string]L7Protocol{ + "mysql": L7_PROTOCOL_MYSQL, + "postgresql": L7_PROTOCOL_POSTGRE, + "oracle": L7_PROTOCOL_ORACLE, + "dameng": L7_PROTOCOL_DAMENG, + "db2": L7_PROTOCOL_DB2, + "tdsql": L7_PROTOCOL_TDSQL, + "oceanbase": L7_PROTOCOL_OCEANBASE, + "goldendb": L7_PROTOCOL_GOLDENDB, + "kingbase": L7_PROTOCOL_KINGBASE, + } + for name, protocol := range expected { + if got := L7ProtocolStringMap[name]; got != protocol { + t.Errorf("L7ProtocolStringMap[%q] = %v, want %v", name, got, protocol) + } + if got := protocol.String(false); got == "N/A" { + t.Errorf("L7Protocol(%d).String(false) = %q", protocol, got) + } + } +} diff --git a/server/libs/flow-metrics/tag.go b/server/libs/flow-metrics/tag.go index 8af667fe902..a1a9780075a 100644 --- a/server/libs/flow-metrics/tag.go +++ b/server/libs/flow-metrics/tag.go @@ -1003,7 +1003,7 @@ func GenTagColumns(code Code) []*ckdb.Column { columns = append(columns, ckdb.NewColumnWithGroupBy("l3_epc_id_1", ckdb.Int32).SetComment("ip4/6_1对应的EPC ID")) } if code&L7Protocol != 0 { - columns = append(columns, ckdb.NewColumnWithGroupBy("l7_protocol", ckdb.UInt8).SetComment("应用协议0: unknown, 1: http, 2: dns, 3: mysql, 4: redis, 5: dubbo, 6: kafka")) + columns = append(columns, ckdb.NewColumnWithGroupBy("l7_protocol", ckdb.UInt8).SetComment("应用协议0: unknown, 20: http1, 21: http2, 40: dubbo, 60: mysql, 61: postgresql, 62: oracle, 63: dameng, 64: db2, 65: tdsql, 66: oceanbase, 67: goldendb, 68: kingbase, 80: redis, 100: kafka")) columns = append(columns, ckdb.NewColumnWithGroupBy("app_service", ckdb.LowCardinalityString)) columns = append(columns, ckdb.NewColumnWithGroupBy("app_instance", ckdb.LowCardinalityString)) columns = append(columns, ckdb.NewColumnWithGroupBy("endpoint", ckdb.String)) diff --git a/server/libs/tracetree/spantrace.go b/server/libs/tracetree/spantrace.go index 1dffb1da87c..c0cea40f1bd 100644 --- a/server/libs/tracetree/spantrace.go +++ b/server/libs/tracetree/spantrace.go @@ -31,7 +31,8 @@ const SPAN_TRACE_VERSION_0x13 = 0x13 // before 20251108 const SPAN_TRACE_VERSION_0x14 = 0x14 // before 20251211 const SPAN_TRACE_VERSION_0x15 = 0x15 // before 20251227 const SPAN_TRACE_VERSION_0x16 = 0x16 -const SPAN_TRACE_VERSION = 0x17 +const SPAN_TRACE_VERSION_0x17 = 0x17 // before 20260507 +const SPAN_TRACE_VERSION = 0x18 type SpanTrace struct { QuerierRegion string // not store, easy to use when calculating @@ -75,6 +76,7 @@ type SpanTrace struct { RequestResource string // notice: will be cut to 255 when write ResponseResult string BizProtocol string + BizResponseCode string ResponseException string RequestId uint64 SyscallTraceIDRequest uint64 @@ -137,6 +139,7 @@ func (t *SpanTrace) Decode(decoder *codec.SimpleDecoder) error { t.RequestResource = decoder.ReadString255() t.ResponseResult = decoder.ReadString255() t.BizProtocol = decoder.ReadString255() + t.BizResponseCode = decoder.ReadString255() t.ResponseException = decoder.ReadString255() t.RequestId = decoder.ReadVarintU64() diff --git a/server/libs/tracetree/tracetree.go b/server/libs/tracetree/tracetree.go index 3ed5dabc815..ade856beaa5 100644 --- a/server/libs/tracetree/tracetree.go +++ b/server/libs/tracetree/tracetree.go @@ -37,7 +37,8 @@ const TRACE_TREE_VERSION_0x19 = 0x19 // before 20260127 const TRACE_TREE_VERSION_0x20 = 0x20 // before 20260129 const TRACE_TREE_VERSION_0x21 = 0x21 // before 20260202 const TRACE_TREE_VERSION_0x22 = 0x22 // before 20260317 -const TRACE_TREE_VERSION = 0x23 +const TRACE_TREE_VERSION_0x23 = 0x23 // before 20260507 +const TRACE_TREE_VERSION = 0x24 func HashSearchIndex(key string) uint64 { return utils.DJBHash(17, key) @@ -56,6 +57,14 @@ type TraceTree struct { encodedTreeNodes []byte } +type EndpointStats struct { + BizResponseCode string `json:"biz_response_code"` + ResponseException string `json:"response_exception"` + ResponseCode uint32 `json:"response_code"` + Total uint32 `json:"total"` + ResponseStatus uint8 `json:"response_status"` +} + type SpanInfo struct { SignalSource uint8 AutoServiceType0 uint8 @@ -66,6 +75,7 @@ type SpanInfo struct { AppService1 string ObservationPoint string Endpoints []string + EndpointStat []EndpointStats IsIPv4 bool IP40 uint32 @@ -82,6 +92,8 @@ type NodeInfo struct { ObservationPoint string Endpoints0 []string Endpoints1 []string + EndpointStat0 []EndpointStats + EndpointStat1 []EndpointStats IsIPv4 bool IP4 uint32 @@ -102,6 +114,7 @@ type TreeNode struct { Topic string QuerierRegion string + BizResponseCode string ResponseException string ResponseDurationSum uint64 ResponseCode uint32 @@ -176,6 +189,14 @@ func (t *TraceTree) Encode() { for _, e := range s.Endpoints { encoder.WriteString255(e) } + encoder.WriteU16(uint16(len(s.EndpointStat))) + for _, e := range s.EndpointStat { + encoder.WriteString255(e.BizResponseCode) + encoder.WriteString255(e.ResponseException) + encoder.WriteVarintU32(e.ResponseCode) + encoder.WriteVarintU32(e.Total) + encoder.WriteU8(e.ResponseStatus) + } encoder.WriteBool(s.IsIPv4) if s.IsIPv4 { @@ -209,6 +230,22 @@ func (t *TraceTree) Encode() { for _, e := range nodeInfo.Endpoints1 { encoder.WriteString255(e) } + encoder.WriteU16(uint16(len(nodeInfo.EndpointStat0))) + for _, e := range nodeInfo.EndpointStat0 { + encoder.WriteString255(e.BizResponseCode) + encoder.WriteString255(e.ResponseException) + encoder.WriteVarintU32(e.ResponseCode) + encoder.WriteVarintU32(e.Total) + encoder.WriteU8(e.ResponseStatus) + } + encoder.WriteU16(uint16(len(nodeInfo.EndpointStat1))) + for _, e := range nodeInfo.EndpointStat1 { + encoder.WriteString255(e.BizResponseCode) + encoder.WriteString255(e.ResponseException) + encoder.WriteVarintU32(e.ResponseCode) + encoder.WriteVarintU32(e.Total) + encoder.WriteU8(e.ResponseStatus) + } encoder.WriteBool(nodeInfo.IsIPv4) if nodeInfo.IsIPv4 { @@ -223,6 +260,7 @@ func (t *TraceTree) Encode() { encoder.WriteU8(node.PseudoLink) encoder.WriteString255(node.Topic) encoder.WriteString255(node.QuerierRegion) + encoder.WriteString255(node.BizResponseCode) encoder.WriteString255(node.ResponseException) encoder.WriteVarintU64(node.ResponseDurationSum) encoder.WriteVarintU32(node.ResponseCode) @@ -237,7 +275,7 @@ func (t *TraceTree) Encode() { func (t *TraceTree) Decode(decoder *codec.SimpleDecoder) error { version := decoder.ReadU8() - if version != TRACE_TREE_VERSION && version != TRACE_TREE_VERSION_0X12 && version != TRACE_TREE_VERSION_0X13 { + if version != TRACE_TREE_VERSION { return fmt.Errorf("trace tree data version is %d expect version is %d", version, TRACE_TREE_VERSION) } t.UID = decoder.ReadU64() @@ -268,6 +306,16 @@ func (t *TraceTree) Decode(decoder *codec.SimpleDecoder) error { for k := 0; k < endpointCount; k++ { s.Endpoints[k] = decoder.ReadString255() } + endpointStatCount := int(decoder.ReadU16()) + s.EndpointStat = make([]EndpointStats, endpointStatCount) + for k := 0; k < endpointStatCount; k++ { + e := &s.EndpointStat[k] + e.BizResponseCode = decoder.ReadString255() + e.ResponseException = decoder.ReadString255() + e.ResponseCode = decoder.ReadVarintU32() + e.Total = decoder.ReadVarintU32() + e.ResponseStatus = decoder.ReadU8() + } s.IsIPv4 = decoder.ReadBool() if s.IsIPv4 { @@ -298,6 +346,27 @@ func (t *TraceTree) Decode(decoder *codec.SimpleDecoder) error { for j := 0; j < endpointCount; j++ { nodeInfo.Endpoints1[j] = decoder.ReadString255() } + endpointStatCount := int(decoder.ReadU16()) + nodeInfo.EndpointStat0 = make([]EndpointStats, endpointStatCount) + for j := 0; j < endpointStatCount; j++ { + e := &nodeInfo.EndpointStat0[j] + e.BizResponseCode = decoder.ReadString255() + e.ResponseException = decoder.ReadString255() + e.ResponseCode = decoder.ReadVarintU32() + e.Total = decoder.ReadVarintU32() + e.ResponseStatus = decoder.ReadU8() + } + endpointStatCount = int(decoder.ReadU16()) + nodeInfo.EndpointStat1 = make([]EndpointStats, endpointStatCount) + for j := 0; j < endpointStatCount; j++ { + e := &nodeInfo.EndpointStat1[j] + e.BizResponseCode = decoder.ReadString255() + e.ResponseException = decoder.ReadString255() + e.ResponseCode = decoder.ReadVarintU32() + e.Total = decoder.ReadVarintU32() + e.ResponseStatus = decoder.ReadU8() + } + nodeInfo.IsIPv4 = decoder.ReadBool() if nodeInfo.IsIPv4 { nodeInfo.IP4 = decoder.ReadU32() @@ -307,15 +376,12 @@ func (t *TraceTree) Decode(decoder *codec.SimpleDecoder) error { } n.ChildIndices = n.ChildIndices[:0] n.Level = 0 - if version == TRACE_TREE_VERSION_0X12 { - n.PseudoLink = 0 - } else { - n.PseudoLink = decoder.ReadU8() - } + n.PseudoLink = decoder.ReadU8() n.UID = "" n.Topic = decoder.ReadString255() + n.QuerierRegion = decoder.ReadString255() if version >= TRACE_TREE_VERSION { - n.QuerierRegion = decoder.ReadString255() + n.BizResponseCode = decoder.ReadString255() } n.ResponseException = decoder.ReadString255() n.ResponseDurationSum = decoder.ReadVarintU64() diff --git a/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol b/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol index 1f2d4b84edc..1839ed61ca4 100644 --- a/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol +++ b/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol @@ -16,6 +16,11 @@ 61 , PostgreSQL , 62 , Oracle , 63 , Dameng , +64 , DB2 , +65 , TDSQL , +66 , OceanBase , +67 , GoldenDB , +68 , Kingbase , 80 , Redis , 81 , MongoDB , 82 , Memcached , diff --git a/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol.ch b/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol.ch new file mode 100644 index 00000000000..35dc22f62c0 --- /dev/null +++ b/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol.ch @@ -0,0 +1,39 @@ +# Value , DisplayName , Description +0 , N/A , +20 , HTTP , +21 , HTTP2 , +40 , Dubbo , +41 , gRPC , +43 , SofaRPC , +44 , FastCGI , +45 , bRPC , +46 , Tars , +47 , Some/IP , +48 , ISO-8583 , +49 , Triple , +50 , NetSign , +60 , MySQL , +61 , PostgreSQL , +62 , Oracle , +63 , 达梦 , +64 , DB2 , +65 , TDSQL , +66 , OceanBase , +67 , GoldenDB , +68 , 人大金仓 , +80 , Redis , +81 , MongoDB , +82 , Memcached , +100 , Kafka , +101 , MQTT , +102 , AMQP , RabbitMQ +103 , OpenWire , ActiveMQ +104 , NATS , +105 , Pulsar , +106 , ZMTP , ZeroMQ +107 , RocketMQ , +108 , WebSphereMQ , +120 , DNS , +121 , TLS , +122 , Ping , +127 , Custom , diff --git a/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol.en b/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol.en new file mode 100644 index 00000000000..1839ed61ca4 --- /dev/null +++ b/server/querier/db_descriptions/clickhouse/tag/enum/l7_protocol.en @@ -0,0 +1,39 @@ +# Value , DisplayName , Description +0 , N/A , +20 , HTTP , +21 , HTTP2 , +40 , Dubbo , +41 , gRPC , +43 , SofaRPC , +44 , FastCGI , +45 , bRPC , +46 , Tars , +47 , Some/IP , +48 , ISO-8583 , +49 , Triple , +50 , NetSign , +60 , MySQL , +61 , PostgreSQL , +62 , Oracle , +63 , Dameng , +64 , DB2 , +65 , TDSQL , +66 , OceanBase , +67 , GoldenDB , +68 , Kingbase , +80 , Redis , +81 , MongoDB , +82 , Memcached , +100 , Kafka , +101 , MQTT , +102 , AMQP , RabbitMQ +103 , OpenWire , ActiveMQ +104 , NATS , +105 , Pulsar , +106 , ZMTP , ZeroMQ +107 , RocketMQ , +108 , WebSphereMQ , +120 , DNS , +121 , TLS , +122 , Ping , +127 , Custom , diff --git a/server/querier/engine/clickhouse/clickhouse_test.go b/server/querier/engine/clickhouse/clickhouse_test.go index 637a727ecc3..c774d38c3d0 100644 --- a/server/querier/engine/clickhouse/clickhouse_test.go +++ b/server/querier/engine/clickhouse/clickhouse_test.go @@ -35,6 +35,7 @@ import ( "github.com/deepflowio/deepflow/server/querier/config" "github.com/deepflowio/deepflow/server/querier/engine/clickhouse/client" "github.com/deepflowio/deepflow/server/querier/engine/clickhouse/metrics" + tagdescription "github.com/deepflowio/deepflow/server/querier/engine/clickhouse/tag" "github.com/deepflowio/deepflow/server/querier/parse" ) @@ -692,6 +693,54 @@ func TestGetSql(t *testing.T) { } } +func TestL7ProtocolDatabaseEnumCandidates(t *testing.T) { + if err := Load(); err != nil { + t.Fatal(err) + } + + enums, ok := tagdescription.TAG_INT_ENUMS["l7_protocol"] + if !ok { + t.Fatal("missing l7_protocol int enum") + } + got := make(map[int]struct { + en string + zh string + }, len(enums)) + for _, item := range enums { + value, ok := item.Value.(int) + if !ok { + t.Fatalf("unexpected l7_protocol enum value type %T", item.Value) + } + got[value] = struct { + en string + zh string + }{ + en: fmt.Sprint(item.DisplayNameEN), + zh: fmt.Sprint(item.DisplayNameZH), + } + } + + expected := map[int]struct { + en string + zh string + }{ + 60: {en: "MySQL", zh: "MySQL"}, + 61: {en: "PostgreSQL", zh: "PostgreSQL"}, + 62: {en: "Oracle", zh: "Oracle"}, + 63: {en: "Dameng", zh: "达梦"}, + 64: {en: "DB2", zh: "DB2"}, + 65: {en: "TDSQL", zh: "TDSQL"}, + 66: {en: "OceanBase", zh: "OceanBase"}, + 67: {en: "GoldenDB", zh: "GoldenDB"}, + 68: {en: "Kingbase", zh: "人大金仓"}, + } + for value, want := range expected { + if got[value] != want { + t.Errorf("l7_protocol enum %d = %+v, want %+v", value, got[value], want) + } + } +} + /* func TestGetSqltest(t *testing.T) { for _, pcase := range parsetest { e := CHEngine{DB: "flow_log"} diff --git a/server/querier/engine/clickhouse/tag/description.go b/server/querier/engine/clickhouse/tag/description.go index 4eafce7f8ad..a720af7a7d0 100644 --- a/server/querier/engine/clickhouse/tag/description.go +++ b/server/querier/engine/clickhouse/tag/description.go @@ -22,6 +22,7 @@ import ( "fmt" "regexp" "slices" + "sort" "strconv" "strings" @@ -156,6 +157,16 @@ func NewTagEnum(value, displayNameZH, displayNameEN, descriptionZH, descriptionE } } +func enumFileBaseAndPriority(file string) (string, int) { + if strings.HasSuffix(file, ".ch") { + return strings.TrimSuffix(file, ".ch"), 1 + } + if strings.HasSuffix(file, ".en") { + return strings.TrimSuffix(file, ".en"), 2 + } + return file, 0 +} + func LoadTagDescriptions(tagData map[string]interface{}) error { // 生成tag description enumFileToTagType := make(map[string]string) @@ -241,7 +252,20 @@ func LoadTagDescriptions(tagData map[string]interface{}) error { tagEnumData, ok := tagData["enum"] if ok { tagMap := map[string][][6]interface{}{} - for tagEnumFile, enumData := range tagEnumData.(map[string]interface{}) { + tagEnumFiles := make([]string, 0, len(tagEnumData.(map[string]interface{}))) + for tagEnumFile := range tagEnumData.(map[string]interface{}) { + tagEnumFiles = append(tagEnumFiles, tagEnumFile) + } + sort.SliceStable(tagEnumFiles, func(i, j int) bool { + baseI, priorityI := enumFileBaseAndPriority(tagEnumFiles[i]) + baseJ, priorityJ := enumFileBaseAndPriority(tagEnumFiles[j]) + if baseI == baseJ { + return priorityI < priorityJ + } + return baseI < baseJ + }) + for _, tagEnumFile := range tagEnumFiles { + enumData := tagEnumData.(map[string]interface{})[tagEnumFile] tagName := strings.TrimSuffix(tagEnumFile, ".ch") tagName = strings.TrimSuffix(tagName, ".en") values, ok := tagMap[tagName]