Skip to content

Commit fbadcd5

Browse files
committed
feat(backend): refactor
1 parent 9866134 commit fbadcd5

5 files changed

Lines changed: 210 additions & 32 deletions

File tree

backend/modules/observability/application/convertor/metric/metric.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44
package metric
55

66
import (
7+
"sort"
8+
"strings"
9+
710
"github.com/coze-dev/coze-loop/backend/kitex_gen/coze/loop/observability/domain/metric"
811
"github.com/coze-dev/coze-loop/backend/modules/observability/domain/metric/entity"
912
"github.com/coze-dev/coze-loop/backend/pkg/lang/ptr"
13+
"github.com/samber/lo"
14+
)
15+
16+
const (
17+
maxPieCount = 10000
18+
retPieCount = 1000
1019
)
1120

1221
func MetricPointDO2DTO(m *entity.MetricPoint) *metric.MetricPoint {
@@ -41,6 +50,9 @@ func MetricDO2DTO(m *entity.Metric) *metric.Metric {
4150
}
4251
ret.TimeSeries[k] = MetricPointListDO2DTO(v)
4352
}
53+
if len(ret.Pie) > retPieCount {
54+
ret.Pie = minimizePie(ret.Pie)
55+
}
4456
return ret
4557
}
4658

@@ -53,3 +65,30 @@ func CompareDTO2DO(c *metric.Compare) *entity.Compare {
5365
Shift: ptr.From(c.ShiftSeconds),
5466
}
5567
}
68+
69+
func minimizePie(pie map[string]string) map[string]string {
70+
if len(pie) > maxPieCount {
71+
for k, v := range pie {
72+
if v == "0" || v == "1" {
73+
delete(pie, k)
74+
}
75+
}
76+
}
77+
if len(pie) > retPieCount {
78+
keys := lo.Keys(pie)
79+
// 假设没有浮点数, pie都是整数
80+
sort.Slice(keys, func(i, j int) bool {
81+
a, b := pie[keys[i]], pie[keys[j]]
82+
if len(a) != len(b) {
83+
return len(a) > len(b)
84+
}
85+
return strings.Compare(a, b) > 0
86+
})
87+
ret := make(map[string]string)
88+
for _, key := range keys[:retPieCount] {
89+
ret[key] = pie[key]
90+
}
91+
pie = ret
92+
}
93+
return pie
94+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) 2026 coze-dev Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metric
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestMinimizePie(t *testing.T) {
14+
t.Run("size <= 1000 should not change", func(t *testing.T) {
15+
pie := make(map[string]string)
16+
for i := 0; i < 500; i++ {
17+
pie[fmt.Sprintf("key-%d", i)] = "1"
18+
}
19+
result := minimizePie(pie)
20+
assert.Equal(t, 500, len(result))
21+
})
22+
23+
t.Run("size > 1000 should truncate by custom sort", func(t *testing.T) {
24+
pie := make(map[string]string)
25+
// Add 1000 items with value "1" (length 1)
26+
for i := 0; i < 1000; i++ {
27+
pie[fmt.Sprintf("small-%d", i)] = "2"
28+
}
29+
30+
// Add 5 items with value "100" (length 3)
31+
for i := 0; i < 5; i++ {
32+
pie[fmt.Sprintf("large-%d", i)] = "100"
33+
}
34+
35+
// Add 5 items with value "10" (length 2)
36+
for i := 0; i < 5; i++ {
37+
pie[fmt.Sprintf("medium-%d", i)] = "91"
38+
}
39+
40+
// Total 1010 items.
41+
// Sort order desc: "100" (len 3) > "10" (len 2) > "1" (len 1)
42+
// Expected kept:
43+
// 5 "large" items (len 3)
44+
// 5 "medium" items (len 2)
45+
// 990 "small" items (len 1)
46+
// Total kept: 1000.
47+
// Removed: 10 "small" items.
48+
49+
result := minimizePie(pie)
50+
assert.Equal(t, 1000, len(result))
51+
52+
// Verify large and medium are kept
53+
for i := 0; i < 5; i++ {
54+
_, ok := result[fmt.Sprintf("large-%d", i)]
55+
assert.True(t, ok, "large item %d missing", i)
56+
_, ok = result[fmt.Sprintf("medium-%d", i)]
57+
assert.True(t, ok, "medium item %d missing", i)
58+
}
59+
60+
// Verify some small items are removed.
61+
// Since map iteration order is random when building the slice,
62+
// and the sort is stable for equal values (depends on implementation, actually sort.Slice is not guaranteed stable,
63+
// but here values are identical "1").
64+
// Wait, if values are identical "1", their relative order is undefined after sort.
65+
// So we can't deterministically say WHICH "small" items are removed, only that 10 are removed.
66+
67+
removedCount := 0
68+
for i := 0; i < 1000; i++ {
69+
if _, ok := result[fmt.Sprintf("small-%d", i)]; !ok {
70+
removedCount++
71+
}
72+
}
73+
fmt.Println("===", removedCount)
74+
assert.Equal(t, 10, removedCount)
75+
})
76+
77+
t.Run("size > 10000 should filter 0 and 1 first", func(t *testing.T) {
78+
pie := make(map[string]string)
79+
// 5000 items with "0"
80+
for i := 0; i < 5000; i++ {
81+
pie[fmt.Sprintf("zero-%d", i)] = "0"
82+
}
83+
// 5000 items with "1"
84+
for i := 0; i < 5000; i++ {
85+
pie[fmt.Sprintf("one-%d", i)] = "1"
86+
}
87+
// 500 items with "2"
88+
for i := 0; i < 500; i++ {
89+
pie[fmt.Sprintf("two-%d", i)] = "2"
90+
}
91+
92+
// Total 10500.
93+
// Filter "0" and "1" -> removes 10000 items.
94+
// Remaining 500 items with "2".
95+
// 500 <= 1000, so no truncation.
96+
97+
result := minimizePie(pie)
98+
assert.Equal(t, 500, len(result))
99+
100+
for k, v := range result {
101+
assert.Equal(t, "2", v)
102+
assert.Contains(t, k, "two-")
103+
}
104+
})
105+
106+
t.Run("size > 10000 filter 0 and 1 then truncate", func(t *testing.T) {
107+
pie := make(map[string]string)
108+
// 9000 items with "1"
109+
for i := 0; i < 9000; i++ {
110+
pie[fmt.Sprintf("one-%d", i)] = "1"
111+
}
112+
// 1500 items with "2"
113+
for i := 0; i < 1500; i++ {
114+
pie[fmt.Sprintf("two-%d", i)] = "2"
115+
}
116+
117+
// Total 10500.
118+
// Filter "1" -> removes 9000 items.
119+
// Remaining 1500 items with "2".
120+
// 1500 > 1000, truncate to 1000.
121+
122+
result := minimizePie(pie)
123+
assert.Equal(t, 1000, len(result))
124+
125+
for _, v := range result {
126+
assert.Equal(t, "2", v)
127+
}
128+
})
129+
}

backend/modules/observability/domain/component/config/config.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,14 @@ type ConsumerListening struct {
114114
}
115115

116116
type MetricQueryConfig struct {
117-
SupportOffline bool `mapstructure:"support_offline" json:"support_offline"`
118-
OfflineCriticalPoint int `mapstructure:"offline_critical_point" json:"offline_critical_point"`
117+
SupportOffline bool `mapstructure:"support_offline" json:"support_offline"`
118+
OfflineCriticalPoint int `mapstructure:"offline_critical_point" json:"offline_critical_point"`
119+
SpaceConfigs map[string]*SpaceConfig `mapstructure:"space_configs" json:"space_configs"`
120+
}
121+
122+
type SpaceConfig struct {
123+
Disabled bool `mapstructure:"disabled" json:"disabled"`
124+
SkipSpaceIDs []string `mapstructure:"skip_space_ids" json:"skip_space_ids"`
119125
}
120126

121127
//go:generate mockgen -destination=mocks/config.go -package=mocks . ITraceConfig

backend/modules/observability/domain/component/config/mocks/config.go

Lines changed: 30 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/modules/observability/domain/metric/service/metric.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ func (m *MetricsService) queryCompoundMetric(ctx context.Context, req *QueryMetr
337337

338338
func (m *MetricsService) queryMetrics(ctx context.Context, req *QueryMetricsReq) (*QueryMetricsResp, error) {
339339
qCfg := m.traceConfig.GetMetricQueryConfig(ctx)
340+
val := qCfg.SpaceConfigs[strconv.FormatInt(req.WorkspaceID, 10)]
341+
if val != nil && val.Disabled {
342+
return &QueryMetricsResp{}, nil
343+
}
340344
if !qCfg.SupportOffline { // 不支持离线指标
341345
return m.queryOnlineMetrics(ctx, req)
342346
} else if m.pMetrics.PlatformMetricDefs[req.PlatformType] == nil { // 不在离线指标计算范围内

0 commit comments

Comments
 (0)