diff --git a/core/hotspot/concurrency_stat_slot.go b/core/hotspot/concurrency_stat_slot.go index ad64ae119..43d3c74f7 100644 --- a/core/hotspot/concurrency_stat_slot.go +++ b/core/hotspot/concurrency_stat_slot.go @@ -44,13 +44,12 @@ func (s *ConcurrencyStatSlot) Order() uint32 { func (c *ConcurrencyStatSlot) OnEntryPassed(ctx *base.EntryContext) { res := ctx.Resource.Name() - args := ctx.Input.Args tcs := getTrafficControllersFor(res) for _, tc := range tcs { if tc.BoundRule().MetricType != Concurrency { continue } - arg := matchArg(tc, args) + arg := tc.ExtractArgs(ctx) if arg == nil { continue } @@ -72,13 +71,12 @@ func (c *ConcurrencyStatSlot) OnEntryBlocked(ctx *base.EntryContext, blockError func (c *ConcurrencyStatSlot) OnCompleted(ctx *base.EntryContext) { res := ctx.Resource.Name() - args := ctx.Input.Args tcs := getTrafficControllersFor(res) for _, tc := range tcs { if tc.BoundRule().MetricType != Concurrency { continue } - arg := matchArg(tc, args) + arg := tc.ExtractArgs(ctx) if arg == nil { continue } diff --git a/core/hotspot/rule.go b/core/hotspot/rule.go index 97917bbe6..f9dc6d1b6 100644 --- a/core/hotspot/rule.go +++ b/core/hotspot/rule.go @@ -80,6 +80,10 @@ type Rule struct { // if ParamIndex is great than or equals to zero, ParamIndex means the -th parameter // if ParamIndex is the negative, ParamIndex means the reversed -th parameter ParamIndex int `json:"paramIndex"` + // ParamKey is the key in EntryContext.Input.Attachments map. + // ParamKey can be used as a supplement to ParamIndex to facilitate rules to quickly obtain parameter from a large number of parameters + // ParamKey is mutually exclusive with ParamIndex, ParamKey has the higher priority than ParamIndex + ParamKey string `json:"paramKey"` // Threshold is the threshold to trigger rejection Threshold int64 `json:"threshold"` // MaxQueueingTimeMs only takes effect when ControlBehavior is Throttling and MetricType is QPS @@ -100,8 +104,8 @@ func (r *Rule) String() string { b, err := json.Marshal(r) if err != nil { // Return the fallback string - return fmt.Sprintf("{Id:%s, Resource:%s, MetricType:%+v, ControlBehavior:%+v, ParamIndex:%d, Threshold:%d, MaxQueueingTimeMs:%d, BurstCount:%d, DurationInSec:%d, ParamsMaxCapacity:%d, SpecificItems:%+v}", - r.ID, r.Resource, r.MetricType, r.ControlBehavior, r.ParamIndex, r.Threshold, r.MaxQueueingTimeMs, r.BurstCount, r.DurationInSec, r.ParamsMaxCapacity, r.SpecificItems) + return fmt.Sprintf("{Id:%s, Resource:%s, MetricType:%+v, ControlBehavior:%+v, ParamIndex:%d, ParamKey:%s, Threshold:%d, MaxQueueingTimeMs:%d, BurstCount:%d, DurationInSec:%d, ParamsMaxCapacity:%d, SpecificItems:%+v}", + r.ID, r.Resource, r.MetricType, r.ControlBehavior, r.ParamIndex, r.ParamKey, r.Threshold, r.MaxQueueingTimeMs, r.BurstCount, r.DurationInSec, r.ParamsMaxCapacity, r.SpecificItems) } return string(b) } @@ -116,7 +120,7 @@ func (r *Rule) IsStatReusable(newRule *Rule) bool { // Equals checks whether current rule is consistent with the given rule. func (r *Rule) Equals(newRule *Rule) bool { - baseCheck := r.Resource == newRule.Resource && r.MetricType == newRule.MetricType && r.ControlBehavior == newRule.ControlBehavior && r.ParamsMaxCapacity == newRule.ParamsMaxCapacity && r.ParamIndex == newRule.ParamIndex && r.Threshold == newRule.Threshold && r.DurationInSec == newRule.DurationInSec && reflect.DeepEqual(r.SpecificItems, newRule.SpecificItems) + baseCheck := r.Resource == newRule.Resource && r.MetricType == newRule.MetricType && r.ControlBehavior == newRule.ControlBehavior && r.ParamsMaxCapacity == newRule.ParamsMaxCapacity && r.ParamIndex == newRule.ParamIndex && r.ParamKey == newRule.ParamKey && r.Threshold == newRule.Threshold && r.DurationInSec == newRule.DurationInSec && reflect.DeepEqual(r.SpecificItems, newRule.SpecificItems) if !baseCheck { return false } diff --git a/core/hotspot/rule_manager.go b/core/hotspot/rule_manager.go index 3c89b3dd7..26f478f48 100644 --- a/core/hotspot/rule_manager.go +++ b/core/hotspot/rule_manager.go @@ -425,12 +425,12 @@ func IsValidRule(rule *Rule) error { if rule.ControlBehavior < 0 { return errors.New("invalid control strategy") } - if rule.ParamIndex < 0 { - return errors.New("invalid param index") - } if rule.MetricType == QPS && rule.DurationInSec <= 0 { return errors.New("invalid duration") } + if rule.ParamIndex > 0 && rule.ParamKey != "" { + return errors.New("invalid param index and param key are mutually exclusive") + } return checkControlBehaviorField(rule) } diff --git a/core/hotspot/rule_test.go b/core/hotspot/rule_test.go index 97e128856..a1af9f225 100644 --- a/core/hotspot/rule_test.go +++ b/core/hotspot/rule_test.go @@ -39,6 +39,7 @@ func Test_Rule_String(t *testing.T) { MetricType: Concurrency, ControlBehavior: Reject, ParamIndex: 0, + ParamKey: "key", Threshold: 110.0, MaxQueueingTimeMs: 5, BurstCount: 10, @@ -46,7 +47,7 @@ func Test_Rule_String(t *testing.T) { ParamsMaxCapacity: 10000, SpecificItems: specific, } - assert.True(t, fmt.Sprintf("%+v", []*Rule{r}) == "[{Id:abc, Resource:abc, MetricType:Concurrency, ControlBehavior:Reject, ParamIndex:0, Threshold:110, MaxQueueingTimeMs:5, BurstCount:10, DurationInSec:1, ParamsMaxCapacity:10000, SpecificItems:map[1123:3 sss:1]}]") + assert.True(t, fmt.Sprintf("%+v", []*Rule{r}) == "[{Id:abc, Resource:abc, MetricType:Concurrency, ControlBehavior:Reject, ParamIndex:0, ParamKey:key, Threshold:110, MaxQueueingTimeMs:5, BurstCount:10, DurationInSec:1, ParamsMaxCapacity:10000, SpecificItems:map[1123:3 sss:1]}]") }) } @@ -61,6 +62,7 @@ func Test_Rule_Equals(t *testing.T) { MetricType: Concurrency, ControlBehavior: Reject, ParamIndex: 0, + ParamKey: "testKey", Threshold: 110.0, MaxQueueingTimeMs: 5, BurstCount: 10, @@ -78,6 +80,7 @@ func Test_Rule_Equals(t *testing.T) { MetricType: Concurrency, ControlBehavior: Reject, ParamIndex: 0, + ParamKey: "testKey", Threshold: 110.0, MaxQueueingTimeMs: 5, BurstCount: 10, diff --git a/core/hotspot/slot.go b/core/hotspot/slot.go index 26fc4c859..f64e97c93 100644 --- a/core/hotspot/slot.go +++ b/core/hotspot/slot.go @@ -16,7 +16,6 @@ package hotspot import ( "github.com/alibaba/sentinel-golang/core/base" - "github.com/alibaba/sentinel-golang/logging" "github.com/alibaba/sentinel-golang/util" ) @@ -40,40 +39,14 @@ func (s *Slot) Order() uint32 { return RuleCheckSlotOrder } -// matchArg matches the arg from args based on TrafficShapingController -// return nil if match failed. -func matchArg(tc TrafficShapingController, args []interface{}) interface{} { - if tc == nil { - return nil - } - idx := tc.BoundParamIndex() - if idx < 0 { - idx = len(args) + idx - } - if idx < 0 { - if logging.DebugEnabled() { - logging.Debug("[Slot matchArg] The param index of hotspot traffic shaping controller is invalid", "args", args, "paramIndex", tc.BoundParamIndex()) - } - return nil - } - if idx >= len(args) { - if logging.DebugEnabled() { - logging.Debug("[Slot matchArg] The argument in index doesn't exist", "args", args, "paramIndex", tc.BoundParamIndex()) - } - return nil - } - return args[idx] -} - func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { res := ctx.Resource.Name() - args := ctx.Input.Args batch := int64(ctx.Input.BatchCount) result := ctx.RuleCheckResult tcs := getTrafficControllersFor(res) for _, tc := range tcs { - arg := matchArg(tc, args) + arg := tc.ExtractArgs(ctx) if arg == nil { continue } diff --git a/core/hotspot/slot_test.go b/core/hotspot/slot_test.go index 717d52a75..2cfa9e7a1 100644 --- a/core/hotspot/slot_test.go +++ b/core/hotspot/slot_test.go @@ -15,11 +15,7 @@ package hotspot import ( - "reflect" - "testing" - "github.com/alibaba/sentinel-golang/core/base" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -52,53 +48,8 @@ func (m *TrafficShapingControllerMock) Replace(r *Rule) { return } -func Test_matchArg(t *testing.T) { - t.Run("Test_matchArg", func(t *testing.T) { - args := make([]interface{}, 10) - args[0] = 1 - args[1] = 2 - args[2] = 3 - args[3] = 4 - args[4] = 5 - args[5] = 6 - args[6] = 7 - args[7] = 8 - args[8] = 9 - args[9] = 10 - - tcMock := &TrafficShapingControllerMock{} - tcMock.On("BoundParamIndex").Return(0) - ret0 := matchArg(tcMock, args) - assert.True(t, reflect.DeepEqual(ret0, 1)) - - tcMock1 := &TrafficShapingControllerMock{} - tcMock1.On("BoundParamIndex").Return(5) - ret1 := matchArg(tcMock1, args) - assert.True(t, reflect.DeepEqual(ret1, 6)) - - tcMock2 := &TrafficShapingControllerMock{} - tcMock2.On("BoundParamIndex").Return(9) - ret2 := matchArg(tcMock2, args) - assert.True(t, reflect.DeepEqual(ret2, 10)) - - tcMock3 := &TrafficShapingControllerMock{} - tcMock3.On("BoundParamIndex").Return(-1) - ret3 := matchArg(tcMock3, args) - assert.True(t, reflect.DeepEqual(ret3, 10)) - - tcMock4 := &TrafficShapingControllerMock{} - tcMock4.On("BoundParamIndex").Return(-10) - ret4 := matchArg(tcMock4, args) - assert.True(t, reflect.DeepEqual(ret4, 1)) - - tcMock5 := &TrafficShapingControllerMock{} - tcMock5.On("BoundParamIndex").Return(10) - ret5 := matchArg(tcMock5, args) - assert.True(t, ret5 == nil) - - tcMock6 := &TrafficShapingControllerMock{} - tcMock6.On("BoundParamIndex").Return(-11) - ret6 := matchArg(tcMock6, args) - assert.True(t, ret6 == nil) - }) +func (m *TrafficShapingControllerMock) ExtractArgs(ctx *base.EntryContext) []interface{} { + _ = m.Called() + ret := []interface{}{ctx.Input.Args[m.BoundParamIndex()]} + return ret } diff --git a/core/hotspot/traffic_shaping.go b/core/hotspot/traffic_shaping.go index 7ba5865c3..dbe163e90 100644 --- a/core/hotspot/traffic_shaping.go +++ b/core/hotspot/traffic_shaping.go @@ -33,6 +33,8 @@ type TrafficShapingController interface { BoundParamIndex() int + ExtractArgs(ctx *base.EntryContext) interface{} + BoundMetric() *ParamsMetric BoundRule() *Rule @@ -44,6 +46,7 @@ type baseTrafficShapingController struct { res string metricType MetricType paramIndex int + paramKey string threshold int64 specificItems map[interface{}]int64 durationInSec int64 @@ -60,6 +63,7 @@ func newBaseTrafficShapingControllerWithMetric(r *Rule, metric *ParamsMetric) *b res: r.Resource, metricType: r.MetricType, paramIndex: r.ParamIndex, + paramKey: r.ParamKey, threshold: r.Threshold, specificItems: r.SpecificItems, durationInSec: r.DurationInSec, @@ -155,6 +159,72 @@ func (c *baseTrafficShapingController) BoundParamIndex() int { return c.paramIndex } +// ExtractArgs matches the arg from ctx based on TrafficShapingController +// return nil if match failed. +func (c *baseTrafficShapingController) ExtractArgs(ctx *base.EntryContext) (value interface{}) { + if c == nil { + return nil + } + value = c.extractAttachmentArgs(ctx) + if value != nil { + return + } + value = c.extractArgs(ctx) + if value != nil { + return + } + return +} +func (c *baseTrafficShapingController) extractArgs(ctx *base.EntryContext) interface{} { + args := ctx.Input.Args + idx := c.BoundParamIndex() + if idx < 0 { + idx = len(args) + idx + } + if idx < 0 { + if logging.DebugEnabled() { + logging.Debug("[extractArgs] The param index of hotspot traffic shaping controller is invalid", + "args", args, "paramIndex", c.BoundParamIndex()) + } + return nil + } + if idx >= len(args) { + if logging.DebugEnabled() { + logging.Debug("[extractArgs] The argument in index doesn't exist", + "args", args, "paramIndex", c.BoundParamIndex()) + } + return nil + } + return args[idx] +} +func (c *baseTrafficShapingController) extractAttachmentArgs(ctx *base.EntryContext) interface{} { + attachments := ctx.Input.Attachments + + if attachments == nil { + if logging.DebugEnabled() { + logging.Debug("[paramKey] The attachments of ctx is nil", + "args", attachments, "paramKey", c.paramKey) + } + return nil + } + if c.paramKey == "" { + if logging.DebugEnabled() { + logging.Debug("[paramKey] The param key is nil", + "args", attachments, "paramKey", c.paramKey) + } + return nil + } + arg, ok := attachments[c.paramKey] + if !ok { + if logging.DebugEnabled() { + logging.Debug("[paramKey] extracted data does not exist", + "args", attachments, "paramKey", c.paramKey) + } + } + + return arg +} + func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, batchCount int64) *base.TokenResult { metric := c.metric if metric == nil { diff --git a/core/hotspot/traffic_shaping_test.go b/core/hotspot/traffic_shaping_test.go index 9f20c945f..3369b0150 100644 --- a/core/hotspot/traffic_shaping_test.go +++ b/core/hotspot/traffic_shaping_test.go @@ -15,10 +15,12 @@ package hotspot import ( + "reflect" "sync/atomic" "testing" "time" + "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -321,3 +323,54 @@ func Test_newBaseTrafficShapingController(t *testing.T) { assert.True(t, tc.metric.RuleTokenCounter.Len() == ParamsMaxCapacity) }) } + +func Test_baseTrafficShapingController_ExtractArgs(t *testing.T) { + t.Run("Test_baseTrafficShapingController_ExtractArgs", func(t *testing.T) { + + c := &baseTrafficShapingController{} + + args := make([]interface{}, 10) + attachments := make(map[interface{}]interface{}) + ctx := base.NewEmptyEntryContext() + ctx.Input = &base.SentinelInput{ + BatchCount: 1, + Flag: 0, + Args: args, + Attachments: attachments, + } + // no data + ret := c.ExtractArgs(ctx) + assert.Nil(t, ret) + + // set data + args[0] = 1 + args[1] = 2 + value1 := "v1" + attachments["test1"] = value1 + + // set index or key + // exist + c.paramIndex = 0 + c.paramKey = "test1" + ret = c.ExtractArgs(ctx) + assert.True(t, reflect.DeepEqual(ret, value1), ret) + + // part exist 1 + c.paramIndex = 10 + c.paramKey = "test1" + ret = c.ExtractArgs(ctx) + assert.True(t, reflect.DeepEqual(ret, value1), ret) + + // part exist 2 + c.paramIndex = 1 + c.paramKey = "test2" + ret = c.ExtractArgs(ctx) + assert.True(t, reflect.DeepEqual(ret, 2), ret) + + // not exist + c.paramIndex = 3 + c.paramKey = "test2" + ret = c.ExtractArgs(ctx) + assert.Nil(t, ret) + }) +} diff --git a/example/hotspot_param_flow/concurrency/hotspot_params_concurrency_example.go b/example/hotspot_param_flow/concurrency/hotspot_params_concurrency_example.go index f9ea4e939..64e8d00c7 100644 --- a/example/hotspot_param_flow/concurrency/hotspot_params_concurrency_example.go +++ b/example/hotspot_param_flow/concurrency/hotspot_params_concurrency_example.go @@ -35,6 +35,7 @@ func main() { if err != nil { log.Fatal(err) } + testKey := "testKey" // the max concurrency is 8 _, err = hotspot.LoadRules([]*hotspot.Rule{ @@ -42,6 +43,7 @@ func main() { Resource: "abc", MetricType: hotspot.Concurrency, ParamIndex: 0, + ParamKey: testKey, Threshold: 8, DurationInSec: 0, }, @@ -79,7 +81,12 @@ func main() { } for { - e, b := sentinel.Entry("abc", sentinel.WithArgs(false, uint32(9))) + e, b := sentinel.Entry("abc", + sentinel.WithArgs(false, uint32(9), + sentinel.WithAttachments(map[interface{}]interface{}{ + testKey: rand.Uint64() % 10, + }), + )) if b != nil { // Blocked. We could get the block reason from the BlockError. time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) diff --git a/example/hotspot_param_flow/qps_reject/hotspot_params_qps_reject_example.go b/example/hotspot_param_flow/qps_reject/hotspot_params_qps_reject_example.go index 0e38e244c..30e1d63dc 100644 --- a/example/hotspot_param_flow/qps_reject/hotspot_params_qps_reject_example.go +++ b/example/hotspot_param_flow/qps_reject/hotspot_params_qps_reject_example.go @@ -15,10 +15,14 @@ package main import ( + "fmt" "log" "math/rand" "time" + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/core/stat" + sentinel "github.com/alibaba/sentinel-golang/api" "github.com/alibaba/sentinel-golang/core/config" "github.com/alibaba/sentinel-golang/core/hotspot" @@ -37,6 +41,8 @@ func main() { if err != nil { log.Fatal(err) } + rand.Seed(time.Now().UnixNano()) + testKey := "testKey" _, err = hotspot.LoadRules([]*hotspot.Rule{ { @@ -57,11 +63,36 @@ func main() { BurstCount: 0, DurationInSec: 1, }, + { + Resource: "efg", + MetricType: hotspot.QPS, + ControlBehavior: hotspot.Reject, + ParamKey: testKey, + Threshold: 50, + BurstCount: 0, + DurationInSec: 1, + }, }) if err != nil { log.Fatalf("Unexpected error: %+v", err) return } + for _, resource := range []string{"abc", "def", "efg"} { + go func(name string) { + node := stat.GetOrCreateResourceNode(name, base.ResTypeCommon) + for { + logging.Info("[HotSpot QPS] "+name, + "pass", node.GetQPS(base.MetricEventPass), + "block", node.GetQPS(base.MetricEventBlock), + "complete", node.GetQPS(base.MetricEventComplete), + "error", node.GetQPS(base.MetricEventError), + "rt", node.GetQPS(base.MetricEventRt), + //"\n total", node.GetQPS(base.MetricEventTotal), + ) + time.Sleep(time.Duration(1000) * time.Millisecond) + } + }(resource) + } logging.Info("[HotSpot Reject] Sentinel Go hot-spot param flow control demo is running. You may see the pass/block metric in the metric log.") for i := 0; i < 10; i++ { @@ -81,15 +112,33 @@ func main() { } }() } + go func() { + for { + e, b := sentinel.Entry("def", sentinel.WithArgs(false, 9, "ahas", fooStruct{rand.Int63() % 5})) + if b != nil { + // Blocked. We could get the block reason from the BlockError. + time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) + } else { + // Passed, wrap the logic here. + time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) + // Be sure the entry is exited finally. + e.Exit() + } + } + }() for { - e, b := sentinel.Entry("def", sentinel.WithArgs(false, 9, "ahas", fooStruct{rand.Int63() % 5})) + val := fmt.Sprintf("test%v", rand.Int31()%10) + e, b := sentinel.Entry("efg", + sentinel.WithAttachments(map[interface{}]interface{}{ + testKey: val, + })) if b != nil { // Blocked. We could get the block reason from the BlockError. time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) } else { // Passed, wrap the logic here. - time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) + time.Sleep(time.Duration(rand.Uint64()%2) * time.Millisecond) // Be sure the entry is exited finally. e.Exit() } @@ -97,4 +146,5 @@ func main() { // The QPS of abc is about: 1500 // The QPS of def is about: 50 + // The QPS of efg is about: 500 } diff --git a/ext/datasource/helper_test.go b/ext/datasource/helper_test.go index 3d55b26fb..becdd881e 100644 --- a/ext/datasource/helper_test.go +++ b/ext/datasource/helper_test.go @@ -404,10 +404,10 @@ func TestHotSpotParamRuleJsonArrayParser(t *testing.T) { for _, r := range rules { fmt.Println(r) } - assert.True(t, strings.Contains(rules[0].String(), "Resource:abc, MetricType:Concurrency, ControlBehavior:Reject, ParamIndex:0, Threshold:1000, MaxQueueingTimeMs:1, BurstCount:10, DurationInSec:1, ParamsMaxCapacity:10000, SpecificItems:map[true:10003 1000:10001 ximu:10002]")) - assert.True(t, strings.Contains(rules[1].String(), "Resource:abc, MetricType:Concurrency, ControlBehavior:Throttling, ParamIndex:1, Threshold:2000, MaxQueueingTimeMs:2, BurstCount:20, DurationInSec:2, ParamsMaxCapacity:20000, SpecificItems:map[true:20003 1000:20001 ximu:20002")) - assert.True(t, strings.Contains(rules[2].String(), "Resource:abc, MetricType:QPS, ControlBehavior:Reject, ParamIndex:2, Threshold:3000, MaxQueueingTimeMs:3, BurstCount:30, DurationInSec:3, ParamsMaxCapacity:30000, SpecificItems:map[true:30003 1000:30001 ximu:30002")) - assert.True(t, strings.Contains(rules[3].String(), "Resource:abc, MetricType:QPS, ControlBehavior:Throttling, ParamIndex:3, Threshold:4000, MaxQueueingTimeMs:4, BurstCount:40, DurationInSec:4, ParamsMaxCapacity:40000, SpecificItems:map[true:40003 1000:40001 ximu:40002")) + assert.True(t, strings.Contains(rules[0].String(), "Resource:abc, MetricType:Concurrency, ControlBehavior:Reject, ParamIndex:0, ParamKey:, Threshold:1000, MaxQueueingTimeMs:1, BurstCount:10, DurationInSec:1, ParamsMaxCapacity:10000, SpecificItems:map[true:10003 1000:10001 ximu:10002]")) + assert.True(t, strings.Contains(rules[1].String(), "Resource:abc, MetricType:Concurrency, ControlBehavior:Throttling, ParamIndex:1, ParamKey:, Threshold:2000, MaxQueueingTimeMs:2, BurstCount:20, DurationInSec:2, ParamsMaxCapacity:20000, SpecificItems:map[true:20003 1000:20001 ximu:20002")) + assert.True(t, strings.Contains(rules[2].String(), "Resource:abc, MetricType:QPS, ControlBehavior:Reject, ParamIndex:2, ParamKey:, Threshold:3000, MaxQueueingTimeMs:3, BurstCount:30, DurationInSec:3, ParamsMaxCapacity:30000, SpecificItems:map[true:30003 1000:30001 ximu:30002")) + assert.True(t, strings.Contains(rules[3].String(), "Resource:abc, MetricType:QPS, ControlBehavior:Throttling, ParamIndex:3, ParamKey:, Threshold:4000, MaxQueueingTimeMs:4, BurstCount:40, DurationInSec:4, ParamsMaxCapacity:40000, SpecificItems:map[true:40003 1000:40001 ximu:40002")) }) t.Run("TestHotSpotParamRuleJsonArrayParser_Nil", func(t *testing.T) { diff --git a/tests/benchmark/hotspot/hotspot_benchmark_test.go b/tests/benchmark/hotspot/hotspot_benchmark_test.go index fca75bbb1..33eea7cad 100644 --- a/tests/benchmark/hotspot/hotspot_benchmark_test.go +++ b/tests/benchmark/hotspot/hotspot_benchmark_test.go @@ -15,10 +15,15 @@ package hotspot import ( + "fmt" "log" "math" + "strconv" + "strings" "testing" + "time" + "github.com/alibaba/sentinel-golang/api" sentinel "github.com/alibaba/sentinel-golang/api" "github.com/alibaba/sentinel-golang/core/hotspot" "github.com/alibaba/sentinel-golang/tests/benchmark" @@ -30,12 +35,18 @@ const ( resThrottling = "resThrottling" ) +var ( + hotspotParamKey = "hotspotParamKey" + hotspotParamValue = "hotspotParamValue" +) + func init() { benchmark.InitSentinel() rule1 := &hotspot.Rule{ Resource: resConcurrency, MetricType: hotspot.Concurrency, ParamIndex: 0, + ParamKey: hotspotParamKey, Threshold: math.MaxInt64, DurationInSec: 0, } @@ -44,6 +55,7 @@ func init() { MetricType: hotspot.QPS, ControlBehavior: hotspot.Reject, ParamIndex: 0, + ParamKey: hotspotParamKey, Threshold: math.MaxInt64, BurstCount: 0, DurationInSec: 1, @@ -53,6 +65,7 @@ func init() { MetricType: hotspot.QPS, ControlBehavior: hotspot.Throttling, ParamIndex: 0, + ParamKey: hotspotParamKey, Threshold: math.MaxInt64, BurstCount: 0, DurationInSec: 1, @@ -64,13 +77,44 @@ func init() { } } -func doCheck(res string) { - if se, err := sentinel.Entry(res); err == nil { +func doCheck(res string, opts ...api.EntryOption) { + if se, err := sentinel.Entry(res, opts...); err == nil { se.Exit() } else { log.Fatalf("Block err: %s", err.Error()) } } +func setRules(n int) { + list := make([]*hotspot.Rule, 0, n) + for i := 0; i < n; i++ { + key := fmt.Sprint(hotspotParamKey, n) + list = append(list, &hotspot.Rule{ + Resource: resReject, + MetricType: hotspot.QPS, + ControlBehavior: hotspot.Reject, + ParamIndex: 0, + ParamKey: key, + Threshold: math.MaxInt64, + BurstCount: 0, + DurationInSec: 1, + }) + } + _, err := hotspot.LoadRules(list) + if err != nil { + panic(err) + } +} +func buildAttachments(n int) map[interface{}]interface{} { + data := make(map[interface{}]interface{}) + buf := strings.Builder{} + for i := 0; i < n; i++ { + buf.Reset() + buf.WriteString(hotspotParamKey) + buf.WriteString(strconv.Itoa(i)) + data[buf.String()] = time.Now().Nanosecond() + } + return data +} func Benchmark_Concurrency_Concurrency4(b *testing.B) { b.ReportAllocs() @@ -78,7 +122,7 @@ func Benchmark_Concurrency_Concurrency4(b *testing.B) { b.SetParallelism(4) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resConcurrency) + doCheck(resConcurrency, nil) } }) } @@ -89,7 +133,7 @@ func Benchmark_Concurrency_Concurrency8(b *testing.B) { b.SetParallelism(8) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resConcurrency) + doCheck(resConcurrency, nil) } }) } @@ -100,7 +144,7 @@ func Benchmark_Concurrency_Concurrency16(b *testing.B) { b.SetParallelism(16) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resConcurrency) + doCheck(resConcurrency, nil) } }) } @@ -111,7 +155,7 @@ func Benchmark_QPS_Reject_Concurrency4(b *testing.B) { b.SetParallelism(4) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resConcurrency) + doCheck(resConcurrency, nil) } }) } @@ -122,7 +166,7 @@ func Benchmark_QPS_Reject_Concurrency8(b *testing.B) { b.SetParallelism(8) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resReject) + doCheck(resReject, nil) } }) } @@ -133,18 +177,37 @@ func Benchmark_QPS_Reject_Concurrency16(b *testing.B) { b.SetParallelism(16) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resReject) + doCheck(resReject, nil) } }) } +func Benchmark_ParamKeyCount_N_QPS_Reject_Concurrency_X(b *testing.B) { + maxArgCount := 6 + setRules(maxArgCount) + + for j := 0; j < maxArgCount; j++ { + name := fmt.Sprintf("mock hited key number=%v", j) + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + doCheck(resReject, sentinel.WithAttachments(buildAttachments(j))) + } + }) + }) + } +} + func Benchmark_QPS_Throttling_Concurrency4(b *testing.B) { b.ReportAllocs() b.ResetTimer() b.SetParallelism(4) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resThrottling) + doCheck(resThrottling, nil) } }) } @@ -155,7 +218,7 @@ func Benchmark_QPS_Throttling_Concurrency8(b *testing.B) { b.SetParallelism(8) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resThrottling) + doCheck(resThrottling, nil) } }) } @@ -166,7 +229,7 @@ func Benchmark_QPS_Throttling_Concurrency16(b *testing.B) { b.SetParallelism(16) b.RunParallel(func(pb *testing.PB) { for pb.Next() { - doCheck(resThrottling) + doCheck(resThrottling, nil) } }) }