diff --git a/go/vt/proto/querythrottler/querythrottler.pb.go b/go/vt/proto/querythrottler/querythrottler.pb.go new file mode 100644 index 00000000000..b9760bdfeaf --- /dev/null +++ b/go/vt/proto/querythrottler/querythrottler.pb.go @@ -0,0 +1,476 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.9 +// protoc v3.21.3 +// source: querythrottler.proto + +package querythrottler + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ThrottlingStrategy represents the strategy used to apply throttling +type ThrottlingStrategy int32 + +const ( + ThrottlingStrategy_UNKNOWN ThrottlingStrategy = 0 + ThrottlingStrategy_TABLET_THROTTLER ThrottlingStrategy = 1 +) + +// Enum value maps for ThrottlingStrategy. +var ( + ThrottlingStrategy_name = map[int32]string{ + 0: "UNKNOWN", + 1: "TABLET_THROTTLER", + } + ThrottlingStrategy_value = map[string]int32{ + "UNKNOWN": 0, + "TABLET_THROTTLER": 1, + } +) + +func (x ThrottlingStrategy) Enum() *ThrottlingStrategy { + p := new(ThrottlingStrategy) + *p = x + return p +} + +func (x ThrottlingStrategy) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ThrottlingStrategy) Descriptor() protoreflect.EnumDescriptor { + return file_querythrottler_proto_enumTypes[0].Descriptor() +} + +func (ThrottlingStrategy) Type() protoreflect.EnumType { + return &file_querythrottler_proto_enumTypes[0] +} + +func (x ThrottlingStrategy) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ThrottlingStrategy.Descriptor instead. +func (ThrottlingStrategy) EnumDescriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{0} +} + +// Config defines the runtime configuration for the IncomingQueryThrottler +type Config struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Strategy ThrottlingStrategy `protobuf:"varint,2,opt,name=strategy,proto3,enum=querythrottler.ThrottlingStrategy" json:"strategy,omitempty"` + TabletStrategyConfig *TabletStrategyConfig `protobuf:"bytes,3,opt,name=tablet_strategy_config,json=tabletStrategyConfig,proto3" json:"tablet_strategy_config,omitempty"` + DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_querythrottler_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_querythrottler_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *Config) GetStrategy() ThrottlingStrategy { + if x != nil { + return x.Strategy + } + return ThrottlingStrategy_UNKNOWN +} + +func (x *Config) GetTabletStrategyConfig() *TabletStrategyConfig { + if x != nil { + return x.TabletStrategyConfig + } + return nil +} + +func (x *Config) GetDryRun() bool { + if x != nil { + return x.DryRun + } + return false +} + +// TabletStrategyConfig holds per-tablet-type throttling rules +type TabletStrategyConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + TabletRules map[string]*StatementRuleSet `protobuf:"bytes,1,rep,name=tablet_rules,json=tabletRules,proto3" json:"tablet_rules,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TabletStrategyConfig) Reset() { + *x = TabletStrategyConfig{} + mi := &file_querythrottler_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TabletStrategyConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TabletStrategyConfig) ProtoMessage() {} + +func (x *TabletStrategyConfig) ProtoReflect() protoreflect.Message { + mi := &file_querythrottler_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TabletStrategyConfig.ProtoReflect.Descriptor instead. +func (*TabletStrategyConfig) Descriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{1} +} + +func (x *TabletStrategyConfig) GetTabletRules() map[string]*StatementRuleSet { + if x != nil { + return x.TabletRules + } + return nil +} + +// StatementRuleSet maps SQL statement types to metric rules +type StatementRuleSet struct { + state protoimpl.MessageState `protogen:"open.v1"` + StatementRules map[string]*MetricRuleSet `protobuf:"bytes,1,rep,name=statement_rules,json=statementRules,proto3" json:"statement_rules,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StatementRuleSet) Reset() { + *x = StatementRuleSet{} + mi := &file_querythrottler_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StatementRuleSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatementRuleSet) ProtoMessage() {} + +func (x *StatementRuleSet) ProtoReflect() protoreflect.Message { + mi := &file_querythrottler_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatementRuleSet.ProtoReflect.Descriptor instead. +func (*StatementRuleSet) Descriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{2} +} + +func (x *StatementRuleSet) GetStatementRules() map[string]*MetricRuleSet { + if x != nil { + return x.StatementRules + } + return nil +} + +// MetricRuleSet maps metric names to their throttling rules +type MetricRuleSet struct { + state protoimpl.MessageState `protogen:"open.v1"` + MetricRules map[string]*MetricRule `protobuf:"bytes,1,rep,name=metric_rules,json=metricRules,proto3" json:"metric_rules,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MetricRuleSet) Reset() { + *x = MetricRuleSet{} + mi := &file_querythrottler_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MetricRuleSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MetricRuleSet) ProtoMessage() {} + +func (x *MetricRuleSet) ProtoReflect() protoreflect.Message { + mi := &file_querythrottler_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MetricRuleSet.ProtoReflect.Descriptor instead. +func (*MetricRuleSet) Descriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{3} +} + +func (x *MetricRuleSet) GetMetricRules() map[string]*MetricRule { + if x != nil { + return x.MetricRules + } + return nil +} + +// MetricRule defines how to throttle based on a specific metric +type MetricRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + Thresholds []*ThrottleThreshold `protobuf:"bytes,1,rep,name=thresholds,proto3" json:"thresholds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MetricRule) Reset() { + *x = MetricRule{} + mi := &file_querythrottler_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MetricRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MetricRule) ProtoMessage() {} + +func (x *MetricRule) ProtoReflect() protoreflect.Message { + mi := &file_querythrottler_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MetricRule.ProtoReflect.Descriptor instead. +func (*MetricRule) Descriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{4} +} + +func (x *MetricRule) GetThresholds() []*ThrottleThreshold { + if x != nil { + return x.Thresholds + } + return nil +} + +// ThrottleThreshold defines a condition for throttling +type ThrottleThreshold struct { + state protoimpl.MessageState `protogen:"open.v1"` + Above float64 `protobuf:"fixed64,1,opt,name=above,proto3" json:"above,omitempty"` + Throttle int32 `protobuf:"varint,2,opt,name=throttle,proto3" json:"throttle,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ThrottleThreshold) Reset() { + *x = ThrottleThreshold{} + mi := &file_querythrottler_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ThrottleThreshold) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThrottleThreshold) ProtoMessage() {} + +func (x *ThrottleThreshold) ProtoReflect() protoreflect.Message { + mi := &file_querythrottler_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThrottleThreshold.ProtoReflect.Descriptor instead. +func (*ThrottleThreshold) Descriptor() ([]byte, []int) { + return file_querythrottler_proto_rawDescGZIP(), []int{5} +} + +func (x *ThrottleThreshold) GetAbove() float64 { + if x != nil { + return x.Above + } + return 0 +} + +func (x *ThrottleThreshold) GetThrottle() int32 { + if x != nil { + return x.Throttle + } + return 0 +} + +var File_querythrottler_proto protoreflect.FileDescriptor + +const file_querythrottler_proto_rawDesc = "" + + "\n" + + "\x14querythrottler.proto\x12\x0equerythrottler\"\xd7\x01\n" + + "\x06Config\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\x12>\n" + + "\bstrategy\x18\x02 \x01(\x0e2\".querythrottler.ThrottlingStrategyR\bstrategy\x12Z\n" + + "\x16tablet_strategy_config\x18\x03 \x01(\v2$.querythrottler.TabletStrategyConfigR\x14tabletStrategyConfig\x12\x17\n" + + "\adry_run\x18\x04 \x01(\bR\x06dryRun\"\xd2\x01\n" + + "\x14TabletStrategyConfig\x12X\n" + + "\ftablet_rules\x18\x01 \x03(\v25.querythrottler.TabletStrategyConfig.TabletRulesEntryR\vtabletRules\x1a`\n" + + "\x10TabletRulesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x126\n" + + "\x05value\x18\x02 \x01(\v2 .querythrottler.StatementRuleSetR\x05value:\x028\x01\"\xd3\x01\n" + + "\x10StatementRuleSet\x12]\n" + + "\x0fstatement_rules\x18\x01 \x03(\v24.querythrottler.StatementRuleSet.StatementRulesEntryR\x0estatementRules\x1a`\n" + + "\x13StatementRulesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + + "\x05value\x18\x02 \x01(\v2\x1d.querythrottler.MetricRuleSetR\x05value:\x028\x01\"\xbe\x01\n" + + "\rMetricRuleSet\x12Q\n" + + "\fmetric_rules\x18\x01 \x03(\v2..querythrottler.MetricRuleSet.MetricRulesEntryR\vmetricRules\x1aZ\n" + + "\x10MetricRulesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x120\n" + + "\x05value\x18\x02 \x01(\v2\x1a.querythrottler.MetricRuleR\x05value:\x028\x01\"O\n" + + "\n" + + "MetricRule\x12A\n" + + "\n" + + "thresholds\x18\x01 \x03(\v2!.querythrottler.ThrottleThresholdR\n" + + "thresholds\"E\n" + + "\x11ThrottleThreshold\x12\x14\n" + + "\x05above\x18\x01 \x01(\x01R\x05above\x12\x1a\n" + + "\bthrottle\x18\x02 \x01(\x05R\bthrottle*7\n" + + "\x12ThrottlingStrategy\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\x14\n" + + "\x10TABLET_THROTTLER\x10\x01B-Z+vitess.io/vitess/go/vt/proto/querythrottlerb\x06proto3" + +var ( + file_querythrottler_proto_rawDescOnce sync.Once + file_querythrottler_proto_rawDescData []byte +) + +func file_querythrottler_proto_rawDescGZIP() []byte { + file_querythrottler_proto_rawDescOnce.Do(func() { + file_querythrottler_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_querythrottler_proto_rawDesc), len(file_querythrottler_proto_rawDesc))) + }) + return file_querythrottler_proto_rawDescData +} + +var file_querythrottler_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_querythrottler_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_querythrottler_proto_goTypes = []any{ + (ThrottlingStrategy)(0), // 0: querythrottler.ThrottlingStrategy + (*Config)(nil), // 1: querythrottler.Config + (*TabletStrategyConfig)(nil), // 2: querythrottler.TabletStrategyConfig + (*StatementRuleSet)(nil), // 3: querythrottler.StatementRuleSet + (*MetricRuleSet)(nil), // 4: querythrottler.MetricRuleSet + (*MetricRule)(nil), // 5: querythrottler.MetricRule + (*ThrottleThreshold)(nil), // 6: querythrottler.ThrottleThreshold + nil, // 7: querythrottler.TabletStrategyConfig.TabletRulesEntry + nil, // 8: querythrottler.StatementRuleSet.StatementRulesEntry + nil, // 9: querythrottler.MetricRuleSet.MetricRulesEntry +} +var file_querythrottler_proto_depIdxs = []int32{ + 0, // 0: querythrottler.Config.strategy:type_name -> querythrottler.ThrottlingStrategy + 2, // 1: querythrottler.Config.tablet_strategy_config:type_name -> querythrottler.TabletStrategyConfig + 7, // 2: querythrottler.TabletStrategyConfig.tablet_rules:type_name -> querythrottler.TabletStrategyConfig.TabletRulesEntry + 8, // 3: querythrottler.StatementRuleSet.statement_rules:type_name -> querythrottler.StatementRuleSet.StatementRulesEntry + 9, // 4: querythrottler.MetricRuleSet.metric_rules:type_name -> querythrottler.MetricRuleSet.MetricRulesEntry + 6, // 5: querythrottler.MetricRule.thresholds:type_name -> querythrottler.ThrottleThreshold + 3, // 6: querythrottler.TabletStrategyConfig.TabletRulesEntry.value:type_name -> querythrottler.StatementRuleSet + 4, // 7: querythrottler.StatementRuleSet.StatementRulesEntry.value:type_name -> querythrottler.MetricRuleSet + 5, // 8: querythrottler.MetricRuleSet.MetricRulesEntry.value:type_name -> querythrottler.MetricRule + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_querythrottler_proto_init() } +func file_querythrottler_proto_init() { + if File_querythrottler_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_querythrottler_proto_rawDesc), len(file_querythrottler_proto_rawDesc)), + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_querythrottler_proto_goTypes, + DependencyIndexes: file_querythrottler_proto_depIdxs, + EnumInfos: file_querythrottler_proto_enumTypes, + MessageInfos: file_querythrottler_proto_msgTypes, + }.Build() + File_querythrottler_proto = out.File + file_querythrottler_proto_goTypes = nil + file_querythrottler_proto_depIdxs = nil +} diff --git a/go/vt/proto/querythrottler/querythrottler_vtproto.pb.go b/go/vt/proto/querythrottler/querythrottler_vtproto.pb.go new file mode 100644 index 00000000000..fdae8651b95 --- /dev/null +++ b/go/vt/proto/querythrottler/querythrottler_vtproto.pb.go @@ -0,0 +1,1451 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: v0.6.1-0.20250313105119-ba97887b0a25 +// source: querythrottler.proto + +package querythrottler + +import ( + binary "encoding/binary" + fmt "fmt" + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + proto "google.golang.org/protobuf/proto" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + io "io" + math "math" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *Config) CloneVT() *Config { + if m == nil { + return (*Config)(nil) + } + r := new(Config) + r.Enabled = m.Enabled + r.Strategy = m.Strategy + r.TabletStrategyConfig = m.TabletStrategyConfig.CloneVT() + r.DryRun = m.DryRun + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Config) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *TabletStrategyConfig) CloneVT() *TabletStrategyConfig { + if m == nil { + return (*TabletStrategyConfig)(nil) + } + r := new(TabletStrategyConfig) + if rhs := m.TabletRules; rhs != nil { + tmpContainer := make(map[string]*StatementRuleSet, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.TabletRules = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *TabletStrategyConfig) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *StatementRuleSet) CloneVT() *StatementRuleSet { + if m == nil { + return (*StatementRuleSet)(nil) + } + r := new(StatementRuleSet) + if rhs := m.StatementRules; rhs != nil { + tmpContainer := make(map[string]*MetricRuleSet, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.StatementRules = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *StatementRuleSet) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *MetricRuleSet) CloneVT() *MetricRuleSet { + if m == nil { + return (*MetricRuleSet)(nil) + } + r := new(MetricRuleSet) + if rhs := m.MetricRules; rhs != nil { + tmpContainer := make(map[string]*MetricRule, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.MetricRules = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *MetricRuleSet) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *MetricRule) CloneVT() *MetricRule { + if m == nil { + return (*MetricRule)(nil) + } + r := new(MetricRule) + if rhs := m.Thresholds; rhs != nil { + tmpContainer := make([]*ThrottleThreshold, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.Thresholds = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *MetricRule) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *ThrottleThreshold) CloneVT() *ThrottleThreshold { + if m == nil { + return (*ThrottleThreshold)(nil) + } + r := new(ThrottleThreshold) + r.Above = m.Above + r.Throttle = m.Throttle + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *ThrottleThreshold) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *Config) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Config) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Config) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.DryRun { + i-- + if m.DryRun { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if m.TabletStrategyConfig != nil { + size, err := m.TabletStrategyConfig.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if m.Strategy != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Strategy)) + i-- + dAtA[i] = 0x10 + } + if m.Enabled { + i-- + if m.Enabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *TabletStrategyConfig) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TabletStrategyConfig) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *TabletStrategyConfig) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.TabletRules) > 0 { + for k := range m.TabletRules { + v := m.TabletRules[k] + baseI := i + size, err := v.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + i -= len(k) + copy(dAtA[i:], k) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = protohelpers.EncodeVarint(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *StatementRuleSet) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatementRuleSet) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *StatementRuleSet) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.StatementRules) > 0 { + for k := range m.StatementRules { + v := m.StatementRules[k] + baseI := i + size, err := v.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + i -= len(k) + copy(dAtA[i:], k) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = protohelpers.EncodeVarint(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *MetricRuleSet) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MetricRuleSet) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *MetricRuleSet) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.MetricRules) > 0 { + for k := range m.MetricRules { + v := m.MetricRules[k] + baseI := i + size, err := v.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + i -= len(k) + copy(dAtA[i:], k) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = protohelpers.EncodeVarint(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *MetricRule) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MetricRule) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *MetricRule) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Thresholds) > 0 { + for iNdEx := len(m.Thresholds) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Thresholds[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ThrottleThreshold) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ThrottleThreshold) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ThrottleThreshold) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Throttle != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Throttle)) + i-- + dAtA[i] = 0x10 + } + if m.Above != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Above)))) + i-- + dAtA[i] = 0x9 + } + return len(dAtA) - i, nil +} + +func (m *Config) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Enabled { + n += 2 + } + if m.Strategy != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Strategy)) + } + if m.TabletStrategyConfig != nil { + l = m.TabletStrategyConfig.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.DryRun { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *TabletStrategyConfig) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.TabletRules) > 0 { + for k, v := range m.TabletRules { + _ = k + _ = v + l = 0 + if v != nil { + l = v.SizeVT() + } + l += 1 + protohelpers.SizeOfVarint(uint64(l)) + mapEntrySize := 1 + len(k) + protohelpers.SizeOfVarint(uint64(len(k))) + l + n += mapEntrySize + 1 + protohelpers.SizeOfVarint(uint64(mapEntrySize)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *StatementRuleSet) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.StatementRules) > 0 { + for k, v := range m.StatementRules { + _ = k + _ = v + l = 0 + if v != nil { + l = v.SizeVT() + } + l += 1 + protohelpers.SizeOfVarint(uint64(l)) + mapEntrySize := 1 + len(k) + protohelpers.SizeOfVarint(uint64(len(k))) + l + n += mapEntrySize + 1 + protohelpers.SizeOfVarint(uint64(mapEntrySize)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *MetricRuleSet) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.MetricRules) > 0 { + for k, v := range m.MetricRules { + _ = k + _ = v + l = 0 + if v != nil { + l = v.SizeVT() + } + l += 1 + protohelpers.SizeOfVarint(uint64(l)) + mapEntrySize := 1 + len(k) + protohelpers.SizeOfVarint(uint64(len(k))) + l + n += mapEntrySize + 1 + protohelpers.SizeOfVarint(uint64(mapEntrySize)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *MetricRule) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Thresholds) > 0 { + for _, e := range m.Thresholds { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *ThrottleThreshold) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Above != 0 { + n += 9 + } + if m.Throttle != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Throttle)) + } + n += len(m.unknownFields) + return n +} + +func (m *Config) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Config: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Config: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Enabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Enabled = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Strategy", wireType) + } + m.Strategy = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Strategy |= ThrottlingStrategy(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TabletStrategyConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TabletStrategyConfig == nil { + m.TabletStrategyConfig = &TabletStrategyConfig{} + } + if err := m.TabletStrategyConfig.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.DryRun = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TabletStrategyConfig) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TabletStrategyConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TabletStrategyConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TabletRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TabletRules == nil { + m.TabletRules = make(map[string]*StatementRuleSet) + } + var mapkey string + var mapvalue *StatementRuleSet + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return protohelpers.ErrInvalidLength + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return protohelpers.ErrInvalidLength + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return protohelpers.ErrInvalidLength + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &StatementRuleSet{} + if err := mapvalue.UnmarshalVT(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.TabletRules[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatementRuleSet) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatementRuleSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatementRuleSet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StatementRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StatementRules == nil { + m.StatementRules = make(map[string]*MetricRuleSet) + } + var mapkey string + var mapvalue *MetricRuleSet + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return protohelpers.ErrInvalidLength + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return protohelpers.ErrInvalidLength + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return protohelpers.ErrInvalidLength + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &MetricRuleSet{} + if err := mapvalue.UnmarshalVT(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.StatementRules[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MetricRuleSet) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MetricRuleSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MetricRuleSet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MetricRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MetricRules == nil { + m.MetricRules = make(map[string]*MetricRule) + } + var mapkey string + var mapvalue *MetricRule + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return protohelpers.ErrInvalidLength + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return protohelpers.ErrInvalidLength + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return protohelpers.ErrInvalidLength + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &MetricRule{} + if err := mapvalue.UnmarshalVT(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.MetricRules[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MetricRule) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MetricRule: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MetricRule: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Thresholds", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Thresholds = append(m.Thresholds, &ThrottleThreshold{}) + if err := m.Thresholds[len(m.Thresholds)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ThrottleThreshold) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ThrottleThreshold: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ThrottleThreshold: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Above", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Above = float64(math.Float64frombits(v)) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Throttle", wireType) + } + m.Throttle = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Throttle |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/go/vt/proto/topodata/topodata.pb.go b/go/vt/proto/topodata/topodata.pb.go index 4597d4350c9..d7c87ed1998 100644 --- a/go/vt/proto/topodata/topodata.pb.go +++ b/go/vt/proto/topodata/topodata.pb.go @@ -32,6 +32,7 @@ import ( reflect "reflect" sync "sync" unsafe "unsafe" + querythrottler "vitess.io/vitess/go/vt/proto/querythrottler" vtorcdata "vitess.io/vitess/go/vt/proto/vtorcdata" vttime "vitess.io/vitess/go/vt/proto/vttime" ) @@ -698,9 +699,11 @@ type Keyspace struct { // tablet's mysqld instance. SidecarDbName string `protobuf:"bytes,10,opt,name=sidecar_db_name,json=sidecarDbName,proto3" json:"sidecar_db_name,omitempty"` // Vtorc is the vtorc keyspace config/state for the keyspace. - VtorcState *vtorcdata.Keyspace `protobuf:"bytes,11,opt,name=vtorc_state,json=vtorcState,proto3" json:"vtorc_state,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + VtorcState *vtorcdata.Keyspace `protobuf:"bytes,11,opt,name=vtorc_state,json=vtorcState,proto3" json:"vtorc_state,omitempty"` + // QueryThrottler provides a flexible throttling configuration that supports multiple throttling strategies beyond the standard tablet throttling. + QueryThrottlerConfig *querythrottler.Config `protobuf:"bytes,12,opt,name=query_throttler_config,json=queryThrottlerConfig,proto3" json:"query_throttler_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Keyspace) Reset() { @@ -782,6 +785,13 @@ func (x *Keyspace) GetVtorcState() *vtorcdata.Keyspace { return nil } +func (x *Keyspace) GetQueryThrottlerConfig() *querythrottler.Config { + if x != nil { + return x.QueryThrottlerConfig + } + return nil +} + // ShardReplication describes the MySQL replication relationships // whithin a cell. type ShardReplication struct { @@ -1191,8 +1201,10 @@ type SrvKeyspace struct { // shards and tablets. This is copied from the global keyspace // object. ThrottlerConfig *ThrottlerConfig `protobuf:"bytes,6,opt,name=throttler_config,json=throttlerConfig,proto3" json:"throttler_config,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // QueryThrottler provides a flexible throttling configuration that supports multiple throttling strategies beyond the standard tablet throttling. + QueryThrottlerConfig *querythrottler.Config `protobuf:"bytes,7,opt,name=query_throttler_config,json=queryThrottlerConfig,proto3" json:"query_throttler_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SrvKeyspace) Reset() { @@ -1239,6 +1251,13 @@ func (x *SrvKeyspace) GetThrottlerConfig() *ThrottlerConfig { return nil } +func (x *SrvKeyspace) GetQueryThrottlerConfig() *querythrottler.Config { + if x != nil { + return x.QueryThrottlerConfig + } + return nil +} + // CellInfo contains information about a cell. CellInfo objects are // stored in the global topology server, and describe how to reach // local topology servers. @@ -1807,7 +1826,7 @@ var File_topodata_proto protoreflect.FileDescriptor const file_topodata_proto_rawDesc = "" + "\n" + - "\x0etopodata.proto\x12\btopodata\x1a\x0fvtorcdata.proto\x1a\fvttime.proto\"2\n" + + "\x0etopodata.proto\x12\btopodata\x1a\x0fvtorcdata.proto\x1a\fvttime.proto\x1a\x14querythrottler.proto\"2\n" + "\bKeyRange\x12\x14\n" + "\x05start\x18\x01 \x01(\fR\x05start\x12\x10\n" + "\x03end\x18\x02 \x01(\fR\x03end\"3\n" + @@ -1858,7 +1877,7 @@ const file_topodata_proto_rawDesc = "" + "tabletType\x12\x14\n" + "\x05cells\x18\x02 \x03(\tR\x05cells\x12#\n" + "\rdenied_tables\x18\x04 \x03(\tR\fdeniedTables\x12\x16\n" + - "\x06frozen\x18\x05 \x01(\bR\x06frozenJ\x04\b\x03\x10\x04J\x04\b\x03\x10\x04J\x04\b\x05\x10\x06\"\x88\x03\n" + + "\x06frozen\x18\x05 \x01(\bR\x06frozenJ\x04\b\x03\x10\x04J\x04\b\x03\x10\x04J\x04\b\x05\x10\x06\"\xd6\x03\n" + "\bKeyspace\x12;\n" + "\rkeyspace_type\x18\x05 \x01(\x0e2\x16.topodata.KeyspaceTypeR\fkeyspaceType\x12#\n" + "\rbase_keyspace\x18\x06 \x01(\tR\fbaseKeyspace\x121\n" + @@ -1868,7 +1887,8 @@ const file_topodata_proto_rawDesc = "" + "\x0fsidecar_db_name\x18\n" + " \x01(\tR\rsidecarDbName\x124\n" + "\vvtorc_state\x18\v \x01(\v2\x13.vtorcdata.KeyspaceR\n" + - "vtorcStateJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05\"\x8b\x01\n" + + "vtorcState\x12L\n" + + "\x16query_throttler_config\x18\f \x01(\v2\x16.querythrottler.ConfigR\x14queryThrottlerConfigJ\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05\"\x8b\x01\n" + "\x10ShardReplication\x125\n" + "\x05nodes\x18\x01 \x03(\v2\x1f.topodata.ShardReplication.NodeR\x05nodes\x1a@\n" + "\x04Node\x128\n" + @@ -1911,12 +1931,13 @@ const file_topodata_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\v2%.topodata.ThrottlerConfig.MetricNamesR\x05value:\x028\x01\x1aC\n" + "\x15MetricThresholdsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x01R\x05value:\x028\x01\"\x98\x03\n" + + "\x05value\x18\x02 \x01(\x01R\x05value:\x028\x01\"\xe6\x03\n" + "\vSrvKeyspace\x12G\n" + "\n" + "partitions\x18\x01 \x03(\v2'.topodata.SrvKeyspace.KeyspacePartitionR\n" + "partitions\x12D\n" + - "\x10throttler_config\x18\x06 \x01(\v2\x19.topodata.ThrottlerConfigR\x0fthrottlerConfig\x1a\xe1\x01\n" + + "\x10throttler_config\x18\x06 \x01(\v2\x19.topodata.ThrottlerConfigR\x0fthrottlerConfig\x12L\n" + + "\x16query_throttler_config\x18\a \x01(\v2\x16.querythrottler.ConfigR\x14queryThrottlerConfig\x1a\xe1\x01\n" + "\x11KeyspacePartition\x125\n" + "\vserved_type\x18\x01 \x01(\x0e2\x14.topodata.TabletTypeR\n" + "servedType\x12C\n" + @@ -2008,6 +2029,7 @@ var file_topodata_proto_goTypes = []any{ (*vttime.Time)(nil), // 30: vttime.Time (*vtorcdata.Shard)(nil), // 31: vtorcdata.Shard (*vtorcdata.Keyspace)(nil), // 32: vtorcdata.Keyspace + (*querythrottler.Config)(nil), // 33: querythrottler.Config } var file_topodata_proto_depIdxs = []int32{ 4, // 0: topodata.Tablet.alias:type_name -> topodata.TabletAlias @@ -2028,32 +2050,34 @@ var file_topodata_proto_depIdxs = []int32{ 30, // 15: topodata.Keyspace.snapshot_time:type_name -> vttime.Time 13, // 16: topodata.Keyspace.throttler_config:type_name -> topodata.ThrottlerConfig 32, // 17: topodata.Keyspace.vtorc_state:type_name -> vtorcdata.Keyspace - 24, // 18: topodata.ShardReplication.nodes:type_name -> topodata.ShardReplication.Node - 2, // 19: topodata.ShardReplicationError.type:type_name -> topodata.ShardReplicationError.Type - 4, // 20: topodata.ShardReplicationError.tablet_alias:type_name -> topodata.TabletAlias - 3, // 21: topodata.ShardReference.key_range:type_name -> topodata.KeyRange - 3, // 22: topodata.ShardTabletControl.key_range:type_name -> topodata.KeyRange - 30, // 23: topodata.ThrottledAppRule.expires_at:type_name -> vttime.Time - 25, // 24: topodata.ThrottlerConfig.throttled_apps:type_name -> topodata.ThrottlerConfig.ThrottledAppsEntry - 27, // 25: topodata.ThrottlerConfig.app_checked_metrics:type_name -> topodata.ThrottlerConfig.AppCheckedMetricsEntry - 28, // 26: topodata.ThrottlerConfig.metric_thresholds:type_name -> topodata.ThrottlerConfig.MetricThresholdsEntry - 29, // 27: topodata.SrvKeyspace.partitions:type_name -> topodata.SrvKeyspace.KeyspacePartition - 13, // 28: topodata.SrvKeyspace.throttler_config:type_name -> topodata.ThrottlerConfig - 17, // 29: topodata.ExternalVitessCluster.topo_config:type_name -> topodata.TopoConfig - 18, // 30: topodata.ExternalClusters.vitess_cluster:type_name -> topodata.ExternalVitessCluster - 3, // 31: topodata.Shard.SourceShard.key_range:type_name -> topodata.KeyRange - 1, // 32: topodata.Shard.TabletControl.tablet_type:type_name -> topodata.TabletType - 4, // 33: topodata.ShardReplication.Node.tablet_alias:type_name -> topodata.TabletAlias - 12, // 34: topodata.ThrottlerConfig.ThrottledAppsEntry.value:type_name -> topodata.ThrottledAppRule - 26, // 35: topodata.ThrottlerConfig.AppCheckedMetricsEntry.value:type_name -> topodata.ThrottlerConfig.MetricNames - 1, // 36: topodata.SrvKeyspace.KeyspacePartition.served_type:type_name -> topodata.TabletType - 10, // 37: topodata.SrvKeyspace.KeyspacePartition.shard_references:type_name -> topodata.ShardReference - 11, // 38: topodata.SrvKeyspace.KeyspacePartition.shard_tablet_controls:type_name -> topodata.ShardTabletControl - 39, // [39:39] is the sub-list for method output_type - 39, // [39:39] is the sub-list for method input_type - 39, // [39:39] is the sub-list for extension type_name - 39, // [39:39] is the sub-list for extension extendee - 0, // [0:39] is the sub-list for field type_name + 33, // 18: topodata.Keyspace.query_throttler_config:type_name -> querythrottler.Config + 24, // 19: topodata.ShardReplication.nodes:type_name -> topodata.ShardReplication.Node + 2, // 20: topodata.ShardReplicationError.type:type_name -> topodata.ShardReplicationError.Type + 4, // 21: topodata.ShardReplicationError.tablet_alias:type_name -> topodata.TabletAlias + 3, // 22: topodata.ShardReference.key_range:type_name -> topodata.KeyRange + 3, // 23: topodata.ShardTabletControl.key_range:type_name -> topodata.KeyRange + 30, // 24: topodata.ThrottledAppRule.expires_at:type_name -> vttime.Time + 25, // 25: topodata.ThrottlerConfig.throttled_apps:type_name -> topodata.ThrottlerConfig.ThrottledAppsEntry + 27, // 26: topodata.ThrottlerConfig.app_checked_metrics:type_name -> topodata.ThrottlerConfig.AppCheckedMetricsEntry + 28, // 27: topodata.ThrottlerConfig.metric_thresholds:type_name -> topodata.ThrottlerConfig.MetricThresholdsEntry + 29, // 28: topodata.SrvKeyspace.partitions:type_name -> topodata.SrvKeyspace.KeyspacePartition + 13, // 29: topodata.SrvKeyspace.throttler_config:type_name -> topodata.ThrottlerConfig + 33, // 30: topodata.SrvKeyspace.query_throttler_config:type_name -> querythrottler.Config + 17, // 31: topodata.ExternalVitessCluster.topo_config:type_name -> topodata.TopoConfig + 18, // 32: topodata.ExternalClusters.vitess_cluster:type_name -> topodata.ExternalVitessCluster + 3, // 33: topodata.Shard.SourceShard.key_range:type_name -> topodata.KeyRange + 1, // 34: topodata.Shard.TabletControl.tablet_type:type_name -> topodata.TabletType + 4, // 35: topodata.ShardReplication.Node.tablet_alias:type_name -> topodata.TabletAlias + 12, // 36: topodata.ThrottlerConfig.ThrottledAppsEntry.value:type_name -> topodata.ThrottledAppRule + 26, // 37: topodata.ThrottlerConfig.AppCheckedMetricsEntry.value:type_name -> topodata.ThrottlerConfig.MetricNames + 1, // 38: topodata.SrvKeyspace.KeyspacePartition.served_type:type_name -> topodata.TabletType + 10, // 39: topodata.SrvKeyspace.KeyspacePartition.shard_references:type_name -> topodata.ShardReference + 11, // 40: topodata.SrvKeyspace.KeyspacePartition.shard_tablet_controls:type_name -> topodata.ShardTabletControl + 41, // [41:41] is the sub-list for method output_type + 41, // [41:41] is the sub-list for method input_type + 41, // [41:41] is the sub-list for extension type_name + 41, // [41:41] is the sub-list for extension extendee + 0, // [0:41] is the sub-list for field type_name } func init() { file_topodata_proto_init() } diff --git a/go/vt/proto/topodata/topodata_vtproto.pb.go b/go/vt/proto/topodata/topodata_vtproto.pb.go index 99954c18585..4bfbce2be48 100644 --- a/go/vt/proto/topodata/topodata_vtproto.pb.go +++ b/go/vt/proto/topodata/topodata_vtproto.pb.go @@ -12,6 +12,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" io "io" math "math" + querythrottler "vitess.io/vitess/go/vt/proto/querythrottler" vtorcdata "vitess.io/vitess/go/vt/proto/vtorcdata" vttime "vitess.io/vitess/go/vt/proto/vttime" ) @@ -210,6 +211,7 @@ func (m *Keyspace) CloneVT() *Keyspace { r.ThrottlerConfig = m.ThrottlerConfig.CloneVT() r.SidecarDbName = m.SidecarDbName r.VtorcState = m.VtorcState.CloneVT() + r.QueryThrottlerConfig = m.QueryThrottlerConfig.CloneVT() if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -435,6 +437,7 @@ func (m *SrvKeyspace) CloneVT() *SrvKeyspace { } r := new(SrvKeyspace) r.ThrottlerConfig = m.ThrottlerConfig.CloneVT() + r.QueryThrottlerConfig = m.QueryThrottlerConfig.CloneVT() if rhs := m.Partitions; rhs != nil { tmpContainer := make([]*SrvKeyspace_KeyspacePartition, len(rhs)) for k, v := range rhs { @@ -1092,6 +1095,16 @@ func (m *Keyspace) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.QueryThrottlerConfig != nil { + size, err := m.QueryThrottlerConfig.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x62 + } if m.VtorcState != nil { size, err := m.VtorcState.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -1725,6 +1738,16 @@ func (m *SrvKeyspace) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.QueryThrottlerConfig != nil { + size, err := m.QueryThrottlerConfig.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } if m.ThrottlerConfig != nil { size, err := m.ThrottlerConfig.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -2224,6 +2247,10 @@ func (m *Keyspace) SizeVT() (n int) { l = m.VtorcState.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if m.QueryThrottlerConfig != nil { + l = m.QueryThrottlerConfig.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -2452,6 +2479,10 @@ func (m *SrvKeyspace) SizeVT() (n int) { l = m.ThrottlerConfig.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if m.QueryThrottlerConfig != nil { + l = m.QueryThrottlerConfig.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -4342,6 +4373,42 @@ func (m *Keyspace) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field QueryThrottlerConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.QueryThrottlerConfig == nil { + m.QueryThrottlerConfig = &querythrottler.Config{} + } + if err := m.QueryThrottlerConfig.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -5868,6 +5935,42 @@ func (m *SrvKeyspace) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field QueryThrottlerConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.QueryThrottlerConfig == nil { + m.QueryThrottlerConfig = &querythrottler.Config{} + } + if err := m.QueryThrottlerConfig.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/go/vt/vttablet/tabletserver/querythrottler/config.go b/go/vt/vttablet/tabletserver/querythrottler/config.go deleted file mode 100644 index a424184abe5..00000000000 --- a/go/vt/vttablet/tabletserver/querythrottler/config.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2025 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package querythrottler - -import "vitess.io/vitess/go/vt/vttablet/tabletserver/querythrottler/registry" - -// Compile-time interface compliance check -var _ registry.StrategyConfig = (*Config)(nil) - -// Config defines the runtime configuration for the QueryThrottler. -// It specifies whether throttling is enabled and which strategy to use. -type Config struct { - // Enabled indicates whether the throttler should actively apply throttling logic. - Enabled bool `json:"enabled"` - - // DryRun indicates whether throttling decisions should be logged but not enforced. - // When true, queries that would be throttled are allowed to proceed, but the - // throttling decision is logged for observability. - DryRun bool `json:"dry_run"` - - // Strategy selects which throttling strategy should be used. - Strategy registry.ThrottlingStrategy `json:"strategy"` -} - -// GetStrategy implements registry.StrategyConfig interface -func (c Config) GetStrategy() registry.ThrottlingStrategy { - return c.Strategy -} diff --git a/go/vt/vttablet/tabletserver/querythrottler/config_loader_interface.go b/go/vt/vttablet/tabletserver/querythrottler/config_loader_interface.go deleted file mode 100644 index ce04ffe7d9c..00000000000 --- a/go/vt/vttablet/tabletserver/querythrottler/config_loader_interface.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2025 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package querythrottler - -import "context" - -type ConfigLoader interface { - // Load returns the latest throttler config (may come from file, topo, etc.) - Load(ctx context.Context) (Config, error) -} diff --git a/go/vt/vttablet/tabletserver/querythrottler/file_based_config_loader.go b/go/vt/vttablet/tabletserver/querythrottler/file_based_config_loader.go deleted file mode 100644 index 68b78149b5c..00000000000 --- a/go/vt/vttablet/tabletserver/querythrottler/file_based_config_loader.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2025 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package querythrottler - -import ( - "context" - "encoding/json" - "os" -) - -const defaultConfigPath = "/config/throttler-config.json" - -var _ ConfigLoader = (*FileBasedConfigLoader)(nil) - -// FileBasedConfigLoader implements ConfigLoader by reading configuration from a JSON file. -type FileBasedConfigLoader struct { - configPath string - readFile func(string) ([]byte, error) - unmarshal func([]byte, interface{}) error -} - -// NewFileBasedConfigLoader creates a new instance of FileBasedConfigLoader. -// It uses the standard config path "/config/throttler-config.json" and standard os.ReadFile and json.Unmarshal functions. -func NewFileBasedConfigLoader() *FileBasedConfigLoader { - return &FileBasedConfigLoader{ - configPath: defaultConfigPath, - readFile: os.ReadFile, - unmarshal: json.Unmarshal, - } -} - -// NewFileBasedConfigLoaderWithDeps creates a new instance with custom dependencies for testing. -// This allows injection of mock functions without global state modification. -func NewFileBasedConfigLoaderWithDeps(configPath string, readFile func(string) ([]byte, error), unmarshal func([]byte, interface{}) error) *FileBasedConfigLoader { - return &FileBasedConfigLoader{ - configPath: configPath, - readFile: readFile, - unmarshal: unmarshal, - } -} - -// Load reads the configuration from the configured file path. -func (f *FileBasedConfigLoader) Load(ctx context.Context) (Config, error) { - data, err := f.readFile(f.configPath) - if err != nil { - return Config{}, err - } - - var cfg Config - if unMarshalErr := f.unmarshal(data, &cfg); unMarshalErr != nil { - return Config{}, unMarshalErr - } - - return cfg, nil -} diff --git a/go/vt/vttablet/tabletserver/querythrottler/file_based_config_loader_test.go b/go/vt/vttablet/tabletserver/querythrottler/file_based_config_loader_test.go deleted file mode 100644 index 2c646e0569e..00000000000 --- a/go/vt/vttablet/tabletserver/querythrottler/file_based_config_loader_test.go +++ /dev/null @@ -1,228 +0,0 @@ -/* -Copyright 2025 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package querythrottler - -import ( - "context" - "encoding/json" - "errors" - "testing" - - "vitess.io/vitess/go/vt/vttablet/tabletserver/querythrottler/registry" - - "github.com/stretchr/testify/require" -) - -func TestNewFileBasedConfigLoader(t *testing.T) { - loader := NewFileBasedConfigLoader() - require.NotNil(t, loader) - require.IsType(t, &FileBasedConfigLoader{}, loader) - require.Equal(t, defaultConfigPath, loader.configPath) -} - -func TestFileBasedConfigLoader_Load(t *testing.T) { - tests := []struct { - name string - configPath string - mockReadFile func(filename string) ([]byte, error) - mockJsonUnmarshal func(data []byte, v interface{}) error - expectedConfig Config - expectedError string - }{ - { - name: "successful config load with minimal config", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return []byte(`{"enabled": true, "strategy": "TabletThrottler"}`), nil - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{ - Enabled: true, - Strategy: registry.ThrottlingStrategyTabletThrottler, - }, - }, - { - name: "successful config load with disabled throttler", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return []byte(`{"enabled": false, "strategy": "TabletThrottler"}`), nil - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{ - Enabled: false, - Strategy: registry.ThrottlingStrategyTabletThrottler, - }, - }, - { - name: "file read error - file not found", - configPath: "/nonexistent/config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/nonexistent/config.json", filename) - return nil, errors.New("no such file or directory") - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{}, - expectedError: "no such file or directory", - }, - { - name: "successful config load with dry run as enabled", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return []byte(`{"enabled": true, "strategy": "TabletThrottler", "dry_run": true}`), nil - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{ - Enabled: true, - Strategy: registry.ThrottlingStrategyTabletThrottler, - DryRun: true, - }, - }, - { - name: "file read error - permission denied", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return nil, errors.New("permission denied") - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{}, - expectedError: "permission denied", - }, - { - name: "json unmarshal error - invalid json", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return []byte(`{"enabled": true`), nil - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{}, - expectedError: "unexpected end of JSON input", - }, - { - name: "json unmarshal error - invalid field type", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return []byte(`{"enabled": "not_a_boolean", "strategy": "TabletThrottler"}`), nil - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{}, - expectedError: "json: cannot unmarshal string into Go struct field Config.enabled of type bool", - }, - { - name: "empty file - should unmarshal to zero value config", - configPath: "/config/throttler-config.json", - mockReadFile: func(filename string) ([]byte, error) { - require.Equal(t, "/config/throttler-config.json", filename) - return []byte(`{}`), nil - }, - mockJsonUnmarshal: func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - }, - expectedConfig: Config{ - Enabled: false, - Strategy: "", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create loader with injected dependencies - loader := NewFileBasedConfigLoaderWithDeps(tt.configPath, tt.mockReadFile, tt.mockJsonUnmarshal) - - // Test - config, err := loader.Load(context.Background()) - - // Assert - if tt.expectedError != "" { - require.Error(t, err) - require.EqualError(t, err, tt.expectedError) - require.Equal(t, tt.expectedConfig, config) - } else { - require.NoError(t, err) - require.Equal(t, tt.expectedConfig, config) - } - }) - } -} - -func TestFileBasedConfigLoader_Load_ConfigPath(t *testing.T) { - // Test that the production loader uses the default config path - var capturedPath string - - mockReadFile := func(filename string) ([]byte, error) { - capturedPath = filename - return []byte(`{"enabled": true, "strategy": "TabletThrottler"}`), nil - } - - mockJsonUnmarshal := func(data []byte, v interface{}) error { - return json.Unmarshal(data, v) - } - - // Test with production constructor (should use default path) - loader := NewFileBasedConfigLoaderWithDeps(defaultConfigPath, mockReadFile, mockJsonUnmarshal) - _, err := loader.Load(context.Background()) - - require.NoError(t, err) - require.Equal(t, "/config/throttler-config.json", capturedPath) -} - -func TestFileBasedConfigLoader_ImplementsConfigLoader(t *testing.T) { - // Verify that FileBasedConfigLoader implements ConfigLoader interface - var _ ConfigLoader = (*FileBasedConfigLoader)(nil) - - // This should compile without issues, proving interface compliance - loader := NewFileBasedConfigLoader() - require.NotNil(t, loader) -} - -func TestNewFileBasedConfigLoaderWithDeps(t *testing.T) { - configPath := "/test/config.json" - mockReadFile := func(string) ([]byte, error) { return nil, nil } - mockUnmarshal := func([]byte, interface{}) error { return nil } - - loader := NewFileBasedConfigLoaderWithDeps(configPath, mockReadFile, mockUnmarshal) - - require.NotNil(t, loader) - require.Equal(t, configPath, loader.configPath) - // Note: We can't directly test function equality, but the constructor should set them -} - -func TestFileBasedConfigLoader_UsesDefaultPath(t *testing.T) { - // Test that the production constructor uses the default path - loader := NewFileBasedConfigLoader() - require.Equal(t, "/config/throttler-config.json", loader.configPath) -} diff --git a/go/vt/vttablet/tabletserver/querythrottler/query_throttler.go b/go/vt/vttablet/tabletserver/querythrottler/query_throttler.go index 7ef6778eb81..1862b37ed9f 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/query_throttler.go +++ b/go/vt/vttablet/tabletserver/querythrottler/query_throttler.go @@ -18,16 +18,21 @@ package querythrottler import ( "context" + "errors" "strconv" "sync" + "sync/atomic" "time" "vitess.io/vitess/go/stats" "vitess.io/vitess/go/vt/servenv" "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/srvtopo" + "vitess.io/vitess/go/vt/topo" "vitess.io/vitess/go/vt/log" querypb "vitess.io/vitess/go/vt/proto/query" + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" topodatapb "vitess.io/vitess/go/vt/proto/topodata" vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/vterrors" @@ -52,32 +57,40 @@ type Stats struct { } type QueryThrottler struct { - ctx context.Context + ctx context.Context + cancelWatchContext context.CancelFunc + throttleClient *throttle.Client tabletConfig *tabletenv.TabletConfig - mu sync.RWMutex - // cfg holds the current configuration for the throttler. - cfg Config - // cfgLoader is responsible for loading the configuration. - cfgLoader ConfigLoader - // strategy is the current throttling strategy handler. - strategy registry.ThrottlingStrategyHandler - env tabletenv.Env - stats Stats + + keyspace string + cell string + srvTopoServer srvtopo.Server + + mu sync.RWMutex + watchStarted atomic.Bool + + // cfg holds the current configuration for the throttler (proto type used directly). + cfg *querythrottlerpb.Config + // strategyHandlerInstance is the current throttling strategy handler instance + strategyHandlerInstance registry.ThrottlingStrategyHandler + env tabletenv.Env + stats Stats } // NewQueryThrottler creates a new query throttler. -func NewQueryThrottler(ctx context.Context, throttler *throttle.Throttler, cfgLoader ConfigLoader, env tabletenv.Env) *QueryThrottler { +func NewQueryThrottler(ctx context.Context, throttler *throttle.Throttler, env tabletenv.Env, alias *topodatapb.TabletAlias, srvTopoServer srvtopo.Server) *QueryThrottler { client := throttle.NewBackgroundClient(throttler, throttlerapp.QueryThrottlerName, base.UndefinedScope) qt := &QueryThrottler{ - ctx: ctx, - throttleClient: client, - tabletConfig: env.Config(), - cfg: Config{}, - cfgLoader: cfgLoader, - strategy: ®istry.NoOpStrategy{}, // default strategy until config is loaded - env: env, + ctx: ctx, + throttleClient: client, + tabletConfig: env.Config(), + cell: alias.GetCell(), + srvTopoServer: srvTopoServer, + cfg: &querythrottlerpb.Config{}, + strategyHandlerInstance: ®istry.NoOpStrategy{}, // default strategy until config is loaded + env: env, stats: Stats{ requestsTotal: env.Exporter().NewCountersWithMultiLabels(queryThrottlerAppName+"Requests", "query throttler requests", []string{"Strategy", "Workload", "Priority"}), requestsThrottled: env.Exporter().NewCountersWithMultiLabels(queryThrottlerAppName+"Throttled", "query throttler requests throttled", []string{"Strategy", "Workload", "Priority", "MetricName", "MetricValue", "DryRun"}), @@ -87,10 +100,7 @@ func NewQueryThrottler(ctx context.Context, throttler *throttle.Throttler, cfgLo } // Start the initial strategy - qt.strategy.Start() - - // starting the loop which will be responsible for refreshing the config. - qt.startConfigRefreshLoop() + qt.strategyHandlerInstance.Start() return qt } @@ -101,12 +111,43 @@ func (qt *QueryThrottler) Shutdown() { qt.mu.Lock() defer qt.mu.Unlock() + // Cancel the watch context to stop the watch goroutine + if qt.cancelWatchContext != nil { + qt.cancelWatchContext() + } + + // Reset the watch started flag to allow restarting the watch if needed + qt.watchStarted.Store(false) + // Stop the current strategy to clean up any background processes - if qt.strategy != nil { - qt.strategy.Stop() + if qt.strategyHandlerInstance != nil { + qt.strategyHandlerInstance.Stop() } } +// InitDBConfig initializes the keyspace for the config watch and loads the initial configuration. +// This method is called by TabletServer during the tablet initialization sequence (see +// go/vt/vttablet/tabletserver/tabletserver.go:InitDBConfig), which happens when: +// - A tablet first starts up +// - A tablet restarts after a crash or upgrade +// - A new tablet node is added to the cluster +// +// Why initial config loading is critical: +// When a tablet starts (or restarts), it needs to immediately have the correct throttling +// configuration from the topology server. Without this, the tablet would run with the default +// NoOp strategy until the next configuration update is pushed to the topology, which could +// result in: +// - Unthrottled queries overwhelming a recovering tablet +// - Inconsistent throttling behavior across the fleet during rolling restarts +// - Missing critical throttling rules during high-load periods +func (qt *QueryThrottler) InitDBConfig(keyspace string) { + qt.keyspace = keyspace + log.Infof("QueryThrottler: initialized with keyspace=%s", keyspace) + + // Start the topo server watch post the keyspace is set. + qt.startSrvKeyspaceWatch() +} + // Throttle checks if the tablet is under heavy load // and enforces throttling by rejecting the incoming request if necessary. // Note: This method performs lock-free reads of config and strategy for optimal performance. @@ -117,9 +158,9 @@ func (qt *QueryThrottler) Throttle(ctx context.Context, tabletType topodatapb.Ta // Lock-free read: for maximum performance in the hot path as cfg and strategy are updated rarely (default once per minute). // They are word-sized and safe for atomic reads; stale data for one query is acceptable and avoids mutex contention in the hot path. tCfg := qt.cfg - tStrategy := qt.strategy + tStrategy := qt.strategyHandlerInstance - if !tCfg.Enabled { + if !tCfg.GetEnabled() { return nil } @@ -141,7 +182,7 @@ func (qt *QueryThrottler) Throttle(ctx context.Context, tabletType topodatapb.Ta }() // Evaluate the throttling decision - decision := qt.strategy.Evaluate(ctx, tabletType, parsedQuery, transactionID, attrs) + decision := qt.strategyHandlerInstance.Evaluate(ctx, tabletType, parsedQuery, transactionID, attrs) // Record evaluate-window latency immediately after Evaluate returns qt.stats.evaluateLatency.Record([]string{strategyName, workload, priorityStr}, startTime) @@ -154,10 +195,10 @@ func (qt *QueryThrottler) Throttle(ctx context.Context, tabletType topodatapb.Ta } // Emit metric of query being throttled. - qt.stats.requestsThrottled.Add([]string{strategyName, workload, priorityStr, decision.MetricName, strconv.FormatFloat(decision.MetricValue, 'f', -1, 64), strconv.FormatBool(tCfg.DryRun)}, 1) + qt.stats.requestsThrottled.Add([]string{strategyName, workload, priorityStr, decision.MetricName, strconv.FormatFloat(decision.MetricValue, 'f', -1, 64), strconv.FormatBool(tCfg.GetDryRun())}, 1) // If dry-run mode is enabled, log the decision but don't throttle - if tCfg.DryRun { + if tCfg.GetDryRun() { log.Warningf("[DRY-RUN] %s, metric name: %s, metric value: %f", decision.Message, decision.MetricName, decision.MetricValue) return nil } @@ -166,6 +207,59 @@ func (qt *QueryThrottler) Throttle(ctx context.Context, tabletType topodatapb.Ta return vterrors.New(vtrpcpb.Code_RESOURCE_EXHAUSTED, decision.Message) } +// startSrvKeyspaceWatch starts watching the SrvKeyspace for event-driven config updates. +// This method performs two critical operations: +// 1. Initial Configuration Load (with retry): +// Fetches the current SrvKeyspace configuration from the topology server using GetSrvKeyspace. +// This is essential for tablets starting up or restarting, as they need immediate access to +// throttling rules without waiting for a configuration change event. +// 2. Watch Establishment: +// Starts a background goroutine that watches for future SrvKeyspace changes using WatchSrvKeyspace. +// This ensures the tablet receives real-time configuration updates throughout its lifecycle. +// +// Thread Safety: This method uses the watchStarted atomic flag to ensure it only runs once, even if called +// concurrently. Only the first caller will actually start the watch; subsequent calls return early. +func (qt *QueryThrottler) startSrvKeyspaceWatch() { + // Pre-flight validation: ensure required fields are set + if qt.srvTopoServer == nil || qt.keyspace == "" { + log.Errorf("QueryThrottler: cannot start SrvKeyspace watch, srvTopoServer=%v, keyspace=%s", qt.srvTopoServer != nil, qt.keyspace) + return + } + + // Phase 1: Load initial configuration with retry logic + // This ensures tablets have the correct throttling config immediately after startup/restart. + // TODO(Siddharth) add retry for this initial load + srvKS, err := qt.srvTopoServer.GetSrvKeyspace(qt.ctx, qt.cell, qt.keyspace) + if err != nil { + log.Warningf("QueryThrottler: failed to load initial config for keyspace=%s (GetSrvKeyspace): %v", qt.keyspace, err) + } + if srvKS == nil { + log.Warningf("QueryThrottler: srv keyspace fetched is nil for keyspace=%s ", qt.keyspace) + } + qt.HandleConfigUpdate(srvKS, nil) + + // Phase 2: Start the watch for future configuration updates + // Always start the watch, even if initial load failed, to enable recovery when config becomes available + + // Only start the watch once (protected by atomic flag) + if !qt.watchStarted.CompareAndSwap(false, true) { + log.Infof("QueryThrottler: SrvKeyspace watch already started for keyspace=%s", qt.keyspace) + return + } + watchCtx, cancel := context.WithCancel(qt.ctx) + qt.cancelWatchContext = cancel + + go func() { + // WatchSrvKeyspace will: + // 1. Provide the current value immediately (may duplicate our GetSrvKeyspace result, but deduped) + // 2. Stream future configuration updates via the callback + // 3. Automatically retry on transient errors (handled by resilient watcher) + qt.srvTopoServer.WatchSrvKeyspace(watchCtx, qt.cell, qt.keyspace, qt.HandleConfigUpdate) + }() + + log.Infof("QueryThrottler: started event-driven watch for SrvKeyspace keyspace=%s cell=%s", qt.keyspace, qt.cell) +} + // extractWorkloadName extracts the workload name from ExecuteOptions. // If no workload name is provided, returns a default value. func extractWorkloadName(options *querypb.ExecuteOptions) string { @@ -203,8 +297,93 @@ func extractPriority(options *querypb.ExecuteOptions) int { return optionsPriority } +// HandleConfigUpdate is the callback invoked when the SrvKeyspace topology changes. +// It loads the updated configuration from the topo server and updates the QueryThrottler's +// strategy and configuration accordingly. +// +// IMPORTANT: This method is designed ONLY to be called as a callback from srvtopo.WatchSrvKeyspace. +// It relies on the resilient watcher's auto-retry behavior (see go/vt/srvtopo/watch.go) and should +// not be called directly from other contexts. +// +// Return value contract (required by WatchSrvKeyspace): +// - true: Continue watching (resilient watcher will auto-retry on transient errors) +// - false: Stop watching permanently (for fatal errors like NoNode, context canceled, or Interrupted) +// +// **NOTE: this method is written with the assumption that this is the only piece of code which will be changing the config of QueryThrottler** +func (qt *QueryThrottler) HandleConfigUpdate(srvks *topodatapb.SrvKeyspace, err error) bool { + // Handle topology errors using a hybrid approach: + // - Permanent errors (NoNode, context canceled): stop watching (return false) + // - Transient errors (network issues, etc.): keep watching (return true, auto-retry will reconnect) + if err != nil { + // Keyspace deleted from topology - stop watching + if topo.IsErrType(err, topo.NoNode) { + log.Warningf("HandleConfigUpdate: keyspace %s deleted or not found, stopping watch", qt.keyspace) + return false + } + + // Context canceled or interrupted - graceful shutdown, stop watching + if errors.Is(err, context.Canceled) || topo.IsErrType(err, topo.Interrupted) { + log.Infof("HandleConfigUpdate: watch stopped (context canceled or interrupted)") + return false + } + + // Transient error (network, temporary topo server issue) - keep watching + // The resilient watcher will automatically retry as defined in go/vt/srvtopo/resilient_server.go:46 + log.Warningf("HandleConfigUpdate: transient topo watch error (will retry): %v", err) + return true + } + + if srvks == nil { + log.Warningf("HandleConfigUpdate: srvks is nil") + return true + } + + // Get the query throttler configuration from the SrvKeyspace that the QueryThrottler uses to manage its throttling behavior. + newCfg := srvks.GetQueryThrottlerConfig() + + // If the config is not changed, return early. + if !isConfigUpdateRequired(qt.cfg, newCfg) { + return true + } + + // No Locking is required because only this function updates the configs of Query Throttler. + needsStrategyChange := qt.cfg.GetStrategy() != newCfg.GetStrategy() + oldStrategyInstance := qt.strategyHandlerInstance + + var newStrategy registry.ThrottlingStrategyHandler + if needsStrategyChange { + // Create the new strategy (doesn't need lock) + newStrategy = selectThrottlingStrategy(newCfg, qt.throttleClient, qt.tabletConfig) + } + + // Acquire write lock only for the actual swap operation. + // Using closure + defer ensures unlock happens even on panic. + func() { + qt.mu.Lock() + defer qt.mu.Unlock() + + if needsStrategyChange { + qt.strategyHandlerInstance = newStrategy + // Start a new strategy after assignment, still under lock for consistency. + if newStrategy != nil { + newStrategy.Start() + } + } + // Always update the configuration + qt.cfg = newCfg + }() + + // Stop the old strategy (if needed) outside the lock to avoid blocking. + if needsStrategyChange && oldStrategyInstance != nil { + oldStrategyInstance.Stop() + } + + log.Infof("HandleConfigUpdate: config updated, strategy=%s, enabled=%v", newCfg.GetStrategy(), newCfg.GetEnabled()) + return true +} + // selectThrottlingStrategy returns the appropriate strategy implementation based on the config. -func selectThrottlingStrategy(cfg Config, client *throttle.Client, tabletConfig *tabletenv.TabletConfig) registry.ThrottlingStrategyHandler { +func selectThrottlingStrategy(cfg *querythrottlerpb.Config, client *throttle.Client, tabletConfig *tabletenv.TabletConfig) registry.ThrottlingStrategyHandler { deps := registry.Deps{ ThrottleClient: client, TabletConfig: tabletConfig, @@ -212,46 +391,21 @@ func selectThrottlingStrategy(cfg Config, client *throttle.Client, tabletConfig return registry.CreateStrategy(cfg, deps) } -// startConfigRefreshLoop launches a background goroutine that refreshes the throttler's configuration -// at the interval specified by QueryThrottlerConfigRefreshInterval. -func (qt *QueryThrottler) startConfigRefreshLoop() { - go func() { - refreshInterval := qt.tabletConfig.QueryThrottlerConfigRefreshInterval - configRefreshTicker := time.NewTicker(refreshInterval) - defer configRefreshTicker.Stop() - - for { - select { - case <-qt.ctx.Done(): - return - case <-configRefreshTicker.C: - newCfg, err := qt.cfgLoader.Load(qt.ctx) - if err != nil { - continue - } - - // Only restart strategy if the strategy type has changed - if qt.cfg.Strategy != newCfg.Strategy { - // Stop the current strategy before switching to a new one - if qt.strategy != nil { - qt.strategy.Stop() - } - - newStrategy := selectThrottlingStrategy(newCfg, qt.throttleClient, qt.tabletConfig) - // Update strategy and start the new one - qt.mu.Lock() - qt.strategy = newStrategy - qt.mu.Unlock() - if qt.strategy != nil { - qt.strategy.Start() - } - } - - // Always update the configuration - qt.mu.Lock() - qt.cfg = newCfg - qt.mu.Unlock() - } - } - }() +// isConfigUpdateRequired checks if the new config is different from the old config. +// This only checks for enabled, strategy name, and dry run because the strategy itself will update the strategy-specific config +// during runtime by having a separate watcher similar to the one used in QueryThrottler. +func isConfigUpdateRequired(oldCfg, newCfg *querythrottlerpb.Config) bool { + if oldCfg.GetEnabled() != newCfg.GetEnabled() { + return true + } + + if oldCfg.GetStrategy() != newCfg.GetStrategy() { + return true + } + + if oldCfg.GetDryRun() != newCfg.GetDryRun() { + return true + } + + return false } diff --git a/go/vt/vttablet/tabletserver/querythrottler/query_throttler_test.go b/go/vt/vttablet/tabletserver/querythrottler/query_throttler_test.go index b3f71d42718..cea66bb44e6 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/query_throttler_test.go +++ b/go/vt/vttablet/tabletserver/querythrottler/query_throttler_test.go @@ -18,15 +18,21 @@ package querythrottler import ( "context" + "errors" "fmt" + "sync" "testing" "time" "vitess.io/vitess/go/stats" "vitess.io/vitess/go/vt/log" querypb "vitess.io/vitess/go/vt/proto/query" + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" topodatapb "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/srvtopo" + "vitess.io/vitess/go/vt/srvtopo/srvtopotest" + "vitess.io/vitess/go/vt/topo" "vitess.io/vitess/go/vt/vttablet/tabletserver/querythrottler/registry" @@ -38,52 +44,15 @@ import ( "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle" ) -func TestNewQueryThrottler_ConfigRefresh(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - config := &tabletenv.TabletConfig{ - QueryThrottlerConfigRefreshInterval: 10 * time.Millisecond, - } - env := tabletenv.NewEnv(vtenv.NewTestEnv(), config, "TestThrottler") - - throttler := &throttle.Throttler{} // use mock if needed - iqt := NewQueryThrottler(ctx, throttler, newFakeConfigLoader(Config{ - Enabled: true, - Strategy: registry.ThrottlingStrategyTabletThrottler, - }), env) - - // Assert initial state (should be NoOpStrategy) - require.NotNil(t, iqt) - iqt.mu.RLock() - initialStrategy := iqt.strategy - iqt.mu.RUnlock() - require.IsType(t, ®istry.NoOpStrategy{}, initialStrategy) - - require.Eventually(t, func() bool { - iqt.mu.RLock() - defer iqt.mu.RUnlock() - - // Assert updated cfg and strategy after config refresh - if !iqt.cfg.Enabled { - return false - } - if iqt.cfg.Strategy != registry.ThrottlingStrategyTabletThrottler { - return false - } - return true - }, 1*time.Second, 10*time.Millisecond, "Config should be refreshed and strategy should be updated") -} - func TestSelectThrottlingStrategy(t *testing.T) { tests := []struct { name string - giveThrottlingStrategy registry.ThrottlingStrategy + giveThrottlingStrategy querythrottlerpb.ThrottlingStrategy expectedType registry.ThrottlingStrategyHandler }{ { name: "Unknown strategy defaults to NoOp", - giveThrottlingStrategy: "some-unknown-string", + giveThrottlingStrategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, expectedType: ®istry.NoOpStrategy{}, }, } @@ -96,7 +65,7 @@ func TestSelectThrottlingStrategy(t *testing.T) { QueryThrottlerConfigRefreshInterval: 10 * time.Millisecond, } - strategy := selectThrottlingStrategy(Config{Enabled: true, Strategy: tt.giveThrottlingStrategy}, mockClient, config) + strategy := selectThrottlingStrategy(&querythrottlerpb.Config{Enabled: true, Strategy: tt.giveThrottlingStrategy}, mockClient, config) require.IsType(t, tt.expectedType, strategy) }) @@ -115,20 +84,19 @@ func TestQueryThrottler_StrategyLifecycleManagement(t *testing.T) { } env := tabletenv.NewEnv(vtenv.NewTestEnv(), config, "TestThrottler") - iqt := NewQueryThrottler(ctx, throttler, newFakeConfigLoader(Config{ - Enabled: true, - Strategy: registry.ThrottlingStrategyTabletThrottler, - }), env) + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + + iqt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) // Verify initial strategy was started (NoOpStrategy in this case) - require.NotNil(t, iqt.strategy) + require.NotNil(t, iqt.strategyHandlerInstance) // Test Shutdown properly stops the strategy iqt.Shutdown() // After shutdown, the strategy should have been stopped // In a real test, we would verify the strategy's Stop method was called - require.NotNil(t, iqt.strategy) // Strategy reference should still exist but be stopped + require.NotNil(t, iqt.strategyHandlerInstance) // Strategy reference should still exist but be stopped } // TestQueryThrottler_Shutdown tests the Shutdown method. @@ -142,10 +110,9 @@ func TestQueryThrottler_Shutdown(t *testing.T) { env := tabletenv.NewEnv(vtenv.NewTestEnv(), config, "TestThrottler") throttler := &throttle.Throttler{} - iqt := NewQueryThrottler(ctx, throttler, newFakeConfigLoader(Config{ - Enabled: false, - Strategy: registry.ThrottlingStrategyTabletThrottler, - }), env) + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + + iqt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) // Should not panic when called multiple times iqt.Shutdown() @@ -153,7 +120,7 @@ func TestQueryThrottler_Shutdown(t *testing.T) { // Should still be able to check the strategy reference iqt.mu.RLock() - strategy := iqt.strategy + strategy := iqt.strategyHandlerInstance iqt.mu.RUnlock() require.NotNil(t, strategy) } @@ -266,18 +233,18 @@ func TestQueryThrottler_DryRunMode(t *testing.T) { // Create throttler with controlled config iqt := &QueryThrottler{ ctx: context.Background(), - cfg: Config{ + cfg: &querythrottlerpb.Config{ Enabled: tt.enabled, DryRun: tt.dryRun, }, - strategy: mockStrategy, - env: env, + env: env, stats: Stats{ requestsTotal: env.Exporter().NewCountersWithMultiLabels(queryThrottlerAppName+"Requests", "TestThrottler requests", []string{"Strategy", "Workload", "Priority"}), requestsThrottled: env.Exporter().NewCountersWithMultiLabels(queryThrottlerAppName+"Throttled", "TestThrottler throttled", []string{"Strategy", "Workload", "Priority", "MetricName", "MetricValue", "DryRun"}), totalLatency: env.Exporter().NewMultiTimings(queryThrottlerAppName+"TotalLatencyMs", "Total latency of QueryThrottler.Throttle in milliseconds", []string{"Strategy", "Workload", "Priority"}), evaluateLatency: env.Exporter().NewMultiTimings(queryThrottlerAppName+"EvaluateLatencyMs", "Latency from Throttle entry to completion of Evaluate in milliseconds", []string{"Strategy", "Workload", "Priority"}), }, + strategyHandlerInstance: mockStrategy, } iqt.stats.requestsTotal.ResetAll() @@ -330,38 +297,6 @@ func TestQueryThrottler_DryRunMode(t *testing.T) { } } -// mockThrottlingStrategy is a test strategy that allows us to control throttling decisions -type mockThrottlingStrategy struct { - decision registry.ThrottleDecision - started bool - stopped bool -} - -func (m *mockThrottlingStrategy) Evaluate(ctx context.Context, targetTabletType topodatapb.TabletType, parsedQuery *sqlparser.ParsedQuery, transactionID int64, attrs registry.QueryAttributes) registry.ThrottleDecision { - return m.decision -} - -func (m *mockThrottlingStrategy) Start() { - m.started = true -} - -func (m *mockThrottlingStrategy) Stop() { - m.stopped = true -} - -func (m *mockThrottlingStrategy) GetStrategyName() string { - return "MockStrategy" -} - -// testLogCapture captures log output for testing -type testLogCapture struct { - logs []string -} - -func (lc *testLogCapture) captureLog(msg string, args ...interface{}) { - lc.logs = append(lc.logs, fmt.Sprintf(msg, args...)) -} - func TestQueryThrottler_extractWorkloadName(t *testing.T) { tests := []struct { name string @@ -480,3 +415,626 @@ func TestQueryThrottler_extractPriority(t *testing.T) { }) } } + +// TestQueryThrottler_HandleConfigUpdate_ErrorHandling verifies callback behavior for different error types. +func TestQueryThrottler_HandleConfigUpdate_ErrorHandling(t *testing.T) { + tests := []struct { + name string + inputErr error + expectedResult bool + description string + }{ + { + name: "ContextCanceledError", + inputErr: context.Canceled, + expectedResult: false, + description: "callback should return false to stop watching on context cancellation", + }, + { + name: "TransientTopoError", + inputErr: errors.New("topo error: transient error"), + expectedResult: true, + description: "callback should return true and continue watching on transient errors", + }, + { + name: "NoNodeError", + inputErr: topo.NewError(topo.NoNode, "keyspace/test_keyspace"), + expectedResult: false, + description: "callback should return false to stop watching when keyspace is deleted (NoNode)", + }, + { + name: "InterruptedError", + inputErr: topo.NewError(topo.Interrupted, "watch interrupted"), + expectedResult: false, + description: "callback should return false to stop watching on Interrupted error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + qt := &QueryThrottler{ + ctx: ctx, + keyspace: "test-keyspace", + cfg: &querythrottlerpb.Config{Enabled: true, Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER}, + strategyHandlerInstance: ®istry.NoOpStrategy{}, + tabletConfig: &tabletenv.TabletConfig{}, + } + + // Create a valid SrvKeyspace matching the test setup (errors are checked before srvks is used) + srvks := createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, false) + + result := qt.HandleConfigUpdate(srvks, tt.inputErr) + + require.Equal(t, tt.expectedResult, result, tt.description) + }) + } +} + +// TestQueryThrottler_HandleConfigUpdate__ConfigExtraction verifies config is properly extracted from SrvKeyspace. +func TestQueryThrottler_HandleConfigUpdate__ConfigExtraction(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + oldCfg := &querythrottlerpb.Config{Enabled: false, Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, DryRun: false} + oldStrategy := ®istry.NoOpStrategy{} + + qt := &QueryThrottler{ + ctx: ctx, + cfg: oldCfg, + strategyHandlerInstance: oldStrategy, + tabletConfig: &tabletenv.TabletConfig{}, + throttleClient: &throttle.Client{}, + } + + // Create SrvKeyspace with different config values + srvks := createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, true) + + result := qt.HandleConfigUpdate(srvks, nil) + + // Should return true to continue watching, config should be extracted from SrvKeyspace + require.True(t, result, "callback should return true and continue watching") + + qt.mu.RLock() + require.True(t, qt.cfg.GetEnabled(), "Enabled should be updated from SrvKeyspace") + require.True(t, qt.cfg.GetDryRun(), "DryRun should be updated from SrvKeyspace") + require.Equal(t, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, qt.cfg.GetStrategy(), "strategy should remain TabletThrottler") + qt.mu.RUnlock() +} + +// TestQueryThrottler_HandleConfigUpdate__SuccessfulConfigUpdate tests successful config update when strategy doesn't change. +func TestQueryThrottler_HandleConfigUpdate__SuccessfulConfigUpdate(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Use a mock strategy to track state changes + oldStrategy := &mockThrottlingStrategy{} + + // Both initial and new config have the same strategy TYPE (no swap expected) + unchangedStrategyType := querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER + + qt := &QueryThrottler{ + ctx: ctx, + cfg: &querythrottlerpb.Config{Enabled: true, Strategy: unchangedStrategyType, DryRun: false}, + strategyHandlerInstance: oldStrategy, + tabletConfig: &tabletenv.TabletConfig{}, + } + + // Create SrvKeyspace with same strategy but DryRun changed + srvks := createTestSrvKeyspace(true, unchangedStrategyType, true) + + result := qt.HandleConfigUpdate(srvks, nil) + + require.True(t, result, "callback should return true") + + qt.mu.RLock() + require.True(t, qt.cfg.GetDryRun(), "DryRun config should be updated") + require.Equal(t, unchangedStrategyType, qt.cfg.GetStrategy(), "strategy type should remain the same") + require.Equal(t, oldStrategy, qt.strategyHandlerInstance, "strategy instance should not change when type is same") + // Verify the old strategy was NOT stopped (no swap occurred) + require.False(t, oldStrategy.stopped, "old strategy should NOT be stopped when type doesn't change") + qt.mu.RUnlock() +} + +// TestQueryThrottler_HandleConfigUpdate__StrategySwitch tests that strategy is properly switched when strategy type changes. +func TestQueryThrottler_HandleConfigUpdate__StrategySwitch(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + oldStrategy := &mockThrottlingStrategy{} + + qt := &QueryThrottler{ + ctx: ctx, + cfg: &querythrottlerpb.Config{Enabled: true, Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER}, + strategyHandlerInstance: oldStrategy, + tabletConfig: &tabletenv.TabletConfig{}, + throttleClient: &throttle.Client{}, + } + + srvks := createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_UNKNOWN, false) + + result := qt.HandleConfigUpdate(srvks, nil) + + // Strategy should be switched + require.True(t, result, "callback should return true") + + qt.mu.RLock() + require.Equal(t, querythrottlerpb.ThrottlingStrategy_UNKNOWN, qt.cfg.GetStrategy(), "config strategy should be updated") + // Old strategy should have been stopped (mocked strategy tracks this) + require.True(t, oldStrategy.stopped, "old strategy should be stopped") + // New strategy should be different instance + newStrategyInstance := qt.strategyHandlerInstance + qt.mu.RUnlock() + + require.NotEqual(t, fmt.Sprintf("%p", oldStrategy), fmt.Sprintf("%p", newStrategyInstance), + "strategy instance should be different after type change") +} + +// TestQueryThrottler_HandleConfigUpdate__NoChange tests that nothing changes when the config is identical. +func TestQueryThrottler_HandleConfigUpdate__NoChange(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + unchangedCfg := &querythrottlerpb.Config{Enabled: true, Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, DryRun: false} + oldStrategy := ®istry.NoOpStrategy{} + + qt := &QueryThrottler{ + ctx: ctx, + cfg: unchangedCfg, + strategyHandlerInstance: oldStrategy, + tabletConfig: &tabletenv.TabletConfig{}, + } + + // Create SrvKeyspace with identical config + srvks := createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, false) + + result := qt.HandleConfigUpdate(srvks, nil) + + // Config and strategy should remain same + require.True(t, result, "callback should return true") + + qt.mu.RLock() + require.Equal(t, unchangedCfg, qt.cfg, "config should remain unchanged") + require.Equal(t, oldStrategy, qt.strategyHandlerInstance, "strategy should remain unchanged") + qt.mu.RUnlock() +} + +// TestIsConfigUpdateRequired tests the isConfigUpdateRequired function. +func TestIsConfigUpdateRequired(t *testing.T) { + tests := []struct { + name string + oldCfg *querythrottlerpb.Config + newCfg *querythrottlerpb.Config + expected bool + }{ + { + name: "No changes - configs identical", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + expected: false, + }, + { + name: "Enabled changed from true to false", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + expected: true, + }, + { + name: "Enabled changed from false to true", + oldCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + expected: true, + }, + { + name: "DryRun changed from false to true", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: true, + }, + expected: true, + }, + { + name: "DryRun changed from true to false", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: true, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + expected: true, + }, + { + name: "Multiple fields changed - Enabled and DryRun", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: true, + }, + expected: true, + }, + { + name: "Multiple fields changed - Enabled and StrategyName", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, + DryRun: false, + }, + expected: true, + }, + { + name: "Multiple fields changed - StrategyName and DryRun", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, + DryRun: true, + }, + expected: true, + }, + { + name: "All three fields changed", + oldCfg: &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, + DryRun: true, + }, + expected: true, + }, + { + name: "All fields false/default - no change", + oldCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, + DryRun: false, + }, + newCfg: &querythrottlerpb.Config{ + Enabled: false, + Strategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, + DryRun: false, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isConfigUpdateRequired(tt.oldCfg, tt.newCfg) + require.Equal(t, tt.expected, result) + }) + } +} + +// TestQueryThrottler_startSrvKeyspaceWatch_InitialLoad tests that initial configuration is loaded successfully when GetSrvKeyspace succeeds. +func TestQueryThrottler_startSrvKeyspaceWatch_InitialLoad(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := tabletenv.NewEnv(vtenv.NewTestEnv(), &tabletenv.TabletConfig{}, "TestThrottler") + + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + srvTopoServer.SrvKeyspace = createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, false) + srvTopoServer.SrvKeyspaceError = nil + + throttler := &throttle.Throttler{} + qt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) + + qt.InitDBConfig("test_keyspace") + + // Verify watch was started + require.Eventually(t, func() bool { + return qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, "Watch should have been started") + + // Verify that the configuration was loaded correctly + require.Eventually(t, func() bool { + qt.mu.RLock() + defer qt.mu.RUnlock() + return qt.cfg.GetEnabled() && + qt.cfg.GetStrategy() == querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER && + !qt.cfg.GetDryRun() + }, 2*time.Second, 10*time.Millisecond, "Config should be loaded correctly: enabled=true, strategy=TabletThrottler, dryRun=false") + + require.Equal(t, "test_keyspace", qt.keyspace, "Keyspace should be set correctly") +} + +// TestQueryThrottler_startSrvKeyspaceWatch_InitialLoadFailure tests that watch starts even when initial GetSrvKeyspace fails. +func TestQueryThrottler_startSrvKeyspaceWatch_InitialLoadFailure(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := tabletenv.NewEnv(vtenv.NewTestEnv(), &tabletenv.TabletConfig{}, "TestThrottler") + + // Configure PassthroughSrvTopoServer to return an error on GetSrvKeyspace + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + srvTopoServer.SrvKeyspace = nil + srvTopoServer.SrvKeyspaceError = errors.New("failed to fetch keyspace") + + throttler := &throttle.Throttler{} + qt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) + + // Initialize with keyspace to trigger startSrvKeyspaceWatch + qt.InitDBConfig("test_keyspace") + + // Verify watch was started despite initial load failure + require.Eventually(t, func() bool { + return qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, "Watch should be started even if initial load fails") + + require.Equal(t, "test_keyspace", qt.keyspace, "Keyspace should be set correctly") + + // Configuration should remain at default (NoOpStrategy) due to failure + require.Eventually(t, func() bool { + qt.mu.RLock() + defer qt.mu.RUnlock() + return !qt.cfg.GetEnabled() + }, 2*time.Second, 10*time.Millisecond, "Config should remain disabled after initial load failure") +} + +// TestQueryThrottler_startSrvKeyspaceWatch_OnlyStartsOnce tests that watch only starts once even with concurrent calls (atomic flag protection). +func TestQueryThrottler_startSrvKeyspaceWatch_OnlyStartsOnce(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := tabletenv.NewEnv(vtenv.NewTestEnv(), &tabletenv.TabletConfig{}, "TestThrottler") + + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + srvTopoServer.SrvKeyspace = createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, false) + srvTopoServer.SrvKeyspaceError = nil + + throttler := &throttle.Throttler{} + qt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) + + qt.InitDBConfig("test_keyspace") + + // Attempt to start the watch multiple times concurrently + const numGoroutines = 10 + startedCount := 0 + var wg sync.WaitGroup + var mu sync.Mutex + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + // Each goroutine tries to start the watch + qt.startSrvKeyspaceWatch() + mu.Lock() + startedCount++ + mu.Unlock() + }() + } + + // Wait for all goroutines to complete + wg.Wait() + + // Verify that the watch was started exactly once (atomic flag prevents multiple starts) + require.Eventually(t, func() bool { + return qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, "Watch should have been started") + + require.Equal(t, numGoroutines, startedCount, "All goroutines should have called startSrvKeyspaceWatch") +} + +// TestQueryThrottler_startSrvKeyspaceWatch_RequiredFieldsValidation tests that watch doesn't start when required fields are missing. +func TestQueryThrottler_startSrvKeyspaceWatch_RequiredFieldsValidation(t *testing.T) { + tests := []struct { + name string + srvTopoServer srvtopo.Server + keyspace string + expectedWatchFlag bool + description string + }{ + { + name: "Nil srvTopoServer prevents watch start", + srvTopoServer: nil, + keyspace: "test_keyspace", + expectedWatchFlag: false, + description: "Watch should not start when srvTopoServer is nil", + }, + { + name: "Empty keyspace prevents watch start", + srvTopoServer: srvtopotest.NewPassthroughSrvTopoServer(), + keyspace: "", + expectedWatchFlag: false, + description: "Watch should not start when keyspace is empty", + }, + { + name: "Valid fields allow watch to start", + srvTopoServer: srvtopotest.NewPassthroughSrvTopoServer(), + keyspace: "test_keyspace", + expectedWatchFlag: true, + description: "Watch should start when all required fields are valid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := tabletenv.NewEnv(vtenv.NewTestEnv(), &tabletenv.TabletConfig{}, "TestThrottler") + + throttler := &throttle.Throttler{} + qt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, tt.srvTopoServer) + + qt.InitDBConfig(tt.keyspace) + + qt.startSrvKeyspaceWatch() + + if tt.expectedWatchFlag { + require.Eventually(t, func() bool { + return qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, tt.description) + } else { + // For negative cases, ensure the watch doesn't start within a reasonable time + require.Never(t, func() bool { + return qt.watchStarted.Load() + }, 500*time.Millisecond, 10*time.Millisecond, tt.description) + } + }) + } +} + +// TestQueryThrottler_startSrvKeyspaceWatch_WatchCallback tests that WatchSrvKeyspace callback receives config updates and HandleConfigUpdate is invoked correctly. +func TestQueryThrottler_startSrvKeyspaceWatch_WatchCallback(t *testing.T) { + tests := []struct { + name string + enabled bool + strategy querythrottlerpb.ThrottlingStrategy + dryRun bool + expectedEnabled bool + expectedStrategy querythrottlerpb.ThrottlingStrategy + expectedDryRun bool + }{ + { + name: "TabletThrottler strategy with enabled and no dry-run", + enabled: true, + strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + dryRun: false, + expectedEnabled: true, + expectedStrategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + expectedDryRun: false, + }, + { + name: "TabletThrottler disabled with dry-run", + enabled: false, + strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + dryRun: true, + expectedEnabled: false, + expectedStrategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, + expectedDryRun: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := tabletenv.NewEnv(vtenv.NewTestEnv(), &tabletenv.TabletConfig{}, "TestThrottler") + + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + srvTopoServer.SrvKeyspace = createTestSrvKeyspace(tt.enabled, tt.strategy, tt.dryRun) + srvTopoServer.SrvKeyspaceError = nil + + throttler := &throttle.Throttler{} + qt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) + + qt.InitDBConfig("test_keyspace") + + // Verify watch was started + require.Eventually(t, func() bool { + return qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, "Watch should have been started") + + // Verify that HandleConfigUpdate was called by checking if the config was updated + require.Eventually(t, func() bool { + qt.mu.RLock() + defer qt.mu.RUnlock() + return qt.cfg.GetEnabled() == tt.expectedEnabled && + qt.cfg.GetStrategy() == tt.expectedStrategy && + qt.cfg.GetDryRun() == tt.expectedDryRun + }, 2*time.Second, 10*time.Millisecond, "Config should be updated correctly after callback is invoked") + }, + ) + } +} + +// TestQueryThrottler_startSrvKeyspaceWatch_ShutdownStopsWatch tests that Shutdown properly cancels the watch context and stops the watch goroutine. +func TestQueryThrottler_startSrvKeyspaceWatch_ShutdownStopsWatch(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := tabletenv.NewEnv(vtenv.NewTestEnv(), &tabletenv.TabletConfig{}, "TestThrottler") + + srvTopoServer := srvtopotest.NewPassthroughSrvTopoServer() + srvTopoServer.SrvKeyspace = createTestSrvKeyspace(true, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, false) + srvTopoServer.SrvKeyspaceError = nil + + throttler := &throttle.Throttler{} + qt := NewQueryThrottler(ctx, throttler, env, &topodatapb.TabletAlias{Cell: "test-cell", Uid: uint32(123)}, srvTopoServer) + + qt.InitDBConfig("test_keyspace") + + // Verify watch was started + require.Eventually(t, func() bool { + return qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, "Watch should have been started before shutdown") + + require.NotNil(t, qt.cancelWatchContext, "Cancel function should be set before shutdown") + + // Call Shutdown to stop the watch + qt.Shutdown() + + // Verify that the watch started flag is reset + require.Eventually(t, func() bool { + return !qt.watchStarted.Load() + }, 2*time.Second, 10*time.Millisecond, "Watch should be marked as not started after shutdown") + + // Verify that the strategy was stopped + qt.mu.RLock() + strategyInstance := qt.strategyHandlerInstance + qt.mu.RUnlock() + require.NotNil(t, strategyInstance, "Strategy instance should still exist after shutdown") + + // Call Shutdown again to ensure it doesn't panic + qt.Shutdown() + + // Verify the watch flag remains false + require.False(t, qt.watchStarted.Load(), "Watch should remain not started after multiple shutdowns") +} diff --git a/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy.go b/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy.go index a6307d1166c..fbb11c87a24 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy.go +++ b/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy.go @@ -23,6 +23,7 @@ import ( "vitess.io/vitess/go/vt/sqlparser" + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) @@ -54,5 +55,5 @@ func (s *NoOpStrategy) Stop() { // GetStrategyName returns the name of the strategy. func (s *NoOpStrategy) GetStrategyName() string { - return string(ThrottlingStrategyUnknown) + return querythrottlerpb.ThrottlingStrategy_UNKNOWN.String() } diff --git a/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy_test.go b/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy_test.go index 7b15efc9a2b..b73bfbac2aa 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy_test.go +++ b/go/vt/vttablet/tabletserver/querythrottler/registry/noop_strategy_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) @@ -94,5 +95,5 @@ func TestNoOpStrategy_Evaluate(t *testing.T) { func TestNoOpStrategy_GetStrategyName(t *testing.T) { strategy := &NoOpStrategy{} - require.Equal(t, string(ThrottlingStrategyUnknown), strategy.GetStrategyName()) + require.Equal(t, querythrottlerpb.ThrottlingStrategy_UNKNOWN.String(), strategy.GetStrategyName()) } diff --git a/go/vt/vttablet/tabletserver/querythrottler/registry/registry.go b/go/vt/vttablet/tabletserver/querythrottler/registry/registry.go index 02ea175cd67..4da1bedba18 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/registry/registry.go +++ b/go/vt/vttablet/tabletserver/querythrottler/registry/registry.go @@ -21,16 +21,17 @@ import ( "sync" "vitess.io/vitess/go/vt/log" + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" ) var ( mu sync.RWMutex - factories = map[ThrottlingStrategy]StrategyFactory{} + factories = map[querythrottlerpb.ThrottlingStrategy]StrategyFactory{} ) // Register registers a new strategy factory with the given name. // Panics if a strategy with the same name is already registered (fail-fast behavior). -func Register(name ThrottlingStrategy, factory StrategyFactory) { +func Register(name querythrottlerpb.ThrottlingStrategy, factory StrategyFactory) { mu.Lock() defer mu.Unlock() @@ -44,7 +45,7 @@ func Register(name ThrottlingStrategy, factory StrategyFactory) { // Get retrieves a strategy factory by name. // Returns the factory and true if found, nil and false otherwise. -func Get(name ThrottlingStrategy) (StrategyFactory, bool) { +func Get(name querythrottlerpb.ThrottlingStrategy) (StrategyFactory, bool) { mu.RLock() defer mu.RUnlock() @@ -56,9 +57,9 @@ func Get(name ThrottlingStrategy) (StrategyFactory, bool) { // Falls back to NoOpStrategy for unknown strategies or factory errors. func CreateStrategy(cfg StrategyConfig, deps Deps) ThrottlingStrategyHandler { // If the requested strategy name is unknown or the factory panics/returns error, CreateStrategy() directly constructs &NoOpStrategy{} as its unconditional fallback. - // Configuration files or runtime switches never list "NoOp" as a valid strategy choice; they list “TabletThrottler”, “Cinnamon”, etc. + // Configuration files or runtime switches never list "NoOp" as a valid strategy choice; they list "TabletThrottler", "Cinnamon", etc. // The design intent is: - // Every “real” strategy must self-register to opt-in. + // Every "real" strategy must self-register to opt-in. // NoOpStrategy must always be available—even before any registration happens—so the registry itself can safely fall back on it. factory, ok := Get(cfg.GetStrategy()) if !ok { @@ -77,11 +78,11 @@ func CreateStrategy(cfg StrategyConfig, deps Deps) ThrottlingStrategyHandler { // ListRegistered returns a list of all registered strategy names. // Useful for debugging and testing. -func ListRegistered() []ThrottlingStrategy { +func ListRegistered() []querythrottlerpb.ThrottlingStrategy { mu.RLock() defer mu.RUnlock() - strategies := make([]ThrottlingStrategy, 0, len(factories)) + strategies := make([]querythrottlerpb.ThrottlingStrategy, 0, len(factories)) for name := range factories { strategies = append(strategies, name) } diff --git a/go/vt/vttablet/tabletserver/querythrottler/registry/registry_test.go b/go/vt/vttablet/tabletserver/querythrottler/registry/registry_test.go index 149c0bd33d2..29b5536c82c 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/registry/registry_test.go +++ b/go/vt/vttablet/tabletserver/querythrottler/registry/registry_test.go @@ -20,13 +20,15 @@ import ( "testing" "github.com/stretchr/testify/require" + + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" ) // reset clears all registered factories. This is used for testing. func reset() { mu.Lock() defer mu.Unlock() - factories = make(map[ThrottlingStrategy]StrategyFactory) + factories = make(map[querythrottlerpb.ThrottlingStrategy]StrategyFactory) } // testStrategyFactory is a simple factory for testing. @@ -44,9 +46,9 @@ func TestRegister(t *testing.T) { // Test successful registration testFactory := testStrategyFactory{} - Register("test-strategy", testFactory) + Register(querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, testFactory) - factory, exists := Get("test-strategy") + factory, exists := Get(querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER) require.True(t, exists) require.Equal(t, testFactory, factory) } @@ -55,43 +57,31 @@ func TestRegisterDuplicate(t *testing.T) { reset() testFactory := testStrategyFactory{} - Register("test-strategy", testFactory) + Register(querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, testFactory) // Should panic on duplicate registration require.Panics(t, func() { - Register("test-strategy", testFactory) + Register(querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, testFactory) }) } func TestGetUnknown(t *testing.T) { reset() - factory, exists := Get("unknown-strategy") + factory, exists := Get(querythrottlerpb.ThrottlingStrategy_UNKNOWN) require.False(t, exists) require.Nil(t, factory) } -// testConfig implements the Config interface for testing -type testConfig struct { - strategy ThrottlingStrategy -} - -func (c testConfig) GetStrategy() ThrottlingStrategy { - return c.strategy -} - -func (c testConfig) GetTabletStrategyConfig() interface{} { - return nil -} - func TestCreateStrategy(t *testing.T) { reset() // Register a test factory - Register("test-strategy", testStrategyFactory{}) + Register(querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, testStrategyFactory{}) - cfg := testConfig{ - strategy: "test-strategy", + cfg := &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, } deps := Deps{} @@ -102,8 +92,9 @@ func TestCreateStrategy(t *testing.T) { func TestCreateStrategyUnknown(t *testing.T) { reset() - cfg := testConfig{ - strategy: "unknown-strategy", + cfg := &querythrottlerpb.Config{ + Enabled: true, + Strategy: querythrottlerpb.ThrottlingStrategy_UNKNOWN, } deps := Deps{} @@ -119,12 +110,10 @@ func TestListRegistered(t *testing.T) { strategies := ListRegistered() require.Empty(t, strategies) - // Register some strategies - Register("strategy1", testStrategyFactory{}) - Register("strategy2", testStrategyFactory{}) + // Register strategies using proto enum values + Register(querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER, testStrategyFactory{}) strategies = ListRegistered() - require.Len(t, strategies, 2) - require.Contains(t, strategies, ThrottlingStrategy("strategy1")) - require.Contains(t, strategies, ThrottlingStrategy("strategy2")) + require.Len(t, strategies, 1) + require.Contains(t, strategies, querythrottlerpb.ThrottlingStrategy_TABLET_THROTTLER) } diff --git a/go/vt/vttablet/tabletserver/querythrottler/registry/throttling_handler.go b/go/vt/vttablet/tabletserver/querythrottler/registry/throttling_handler.go index eda19281faf..2942aac52d2 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/registry/throttling_handler.go +++ b/go/vt/vttablet/tabletserver/querythrottler/registry/throttling_handler.go @@ -24,21 +24,6 @@ import ( topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) -// Predefined throttling strategies for the QueryThrottler. -const ( - // ThrottlingStrategyTabletThrottler uses Vitess Tablet Throttler to shed load - // from incoming queries when the tablet is under pressure. - // Reference: https://vitess.io/docs/21.0/reference/features/tablet-throttler/ - ThrottlingStrategyTabletThrottler ThrottlingStrategy = "TabletThrottler" - - // ThrottlingStrategyUnknown is used when the strategy is not known. - ThrottlingStrategyUnknown ThrottlingStrategy = "Unknown" -) - -// ThrottlingStrategy represents the strategy used to apply throttling -// to incoming queries based on system load or external signals. -type ThrottlingStrategy string - // ThrottlingStrategyHandler defines the interface for throttling strategies // used by the QueryThrottler. Each strategy encapsulates its own logic // to determine whether throttling should be applied for an incoming query. diff --git a/go/vt/vttablet/tabletserver/querythrottler/registry/types.go b/go/vt/vttablet/tabletserver/querythrottler/registry/types.go index 69b5d36079b..decc1278e3a 100644 --- a/go/vt/vttablet/tabletserver/querythrottler/registry/types.go +++ b/go/vt/vttablet/tabletserver/querythrottler/registry/types.go @@ -17,6 +17,7 @@ limitations under the License. package registry import ( + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle" ) @@ -55,9 +56,11 @@ type ThrottleDecision struct { } // StrategyConfig defines the configuration interface that strategy implementations -// must satisfy. This avoids circular imports by using a generic interface. +// must satisfy. This matches the proto querythrottlerpb.Config interface. type StrategyConfig interface { - GetStrategy() ThrottlingStrategy + GetEnabled() bool + GetDryRun() bool + GetStrategy() querythrottlerpb.ThrottlingStrategy } // Deps holds the dependencies required by strategy factories. diff --git a/go/vt/vttablet/tabletserver/querythrottler/test_utils.go b/go/vt/vttablet/tabletserver/querythrottler/test_utils.go new file mode 100644 index 00000000000..355c791fa7d --- /dev/null +++ b/go/vt/vttablet/tabletserver/querythrottler/test_utils.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package querythrottler + +import ( + "context" + "fmt" + + querythrottlerpb "vitess.io/vitess/go/vt/proto/querythrottler" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vttablet/tabletserver/querythrottler/registry" +) + +// createTestSrvKeyspace creates a SrvKeyspace with query throttler config for testing +func createTestSrvKeyspace(enabled bool, strategy querythrottlerpb.ThrottlingStrategy, dryRun bool) *topodatapb.SrvKeyspace { + return &topodatapb.SrvKeyspace{ + QueryThrottlerConfig: &querythrottlerpb.Config{ + Enabled: enabled, + Strategy: strategy, + DryRun: dryRun, + }, + } +} + +// mockThrottlingStrategy is a test strategy that allows us to control throttling decisions +type mockThrottlingStrategy struct { + decision registry.ThrottleDecision + started bool + stopped bool +} + +func (m *mockThrottlingStrategy) Evaluate(ctx context.Context, targetTabletType topodatapb.TabletType, parsedQuery *sqlparser.ParsedQuery, transactionID int64, attrs registry.QueryAttributes) registry.ThrottleDecision { + return m.decision +} + +func (m *mockThrottlingStrategy) Start() { + m.started = true +} + +func (m *mockThrottlingStrategy) Stop() { + m.stopped = true +} + +func (m *mockThrottlingStrategy) GetStrategyName() string { + return "MockStrategy" +} + +// testLogCapture captures log output for testing +type testLogCapture struct { + logs []string +} + +func (lc *testLogCapture) captureLog(msg string, args ...interface{}) { + lc.logs = append(lc.logs, fmt.Sprintf(msg, args...)) +} diff --git a/go/vt/vttablet/tabletserver/querythrottler/test_wrappers.go b/go/vt/vttablet/tabletserver/querythrottler/test_wrappers.go deleted file mode 100644 index 85f74eb6080..00000000000 --- a/go/vt/vttablet/tabletserver/querythrottler/test_wrappers.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2025 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package querythrottler - -import "context" - -// fakeConfigLoader is a test fake that implements ConfigLoader. -type fakeConfigLoader struct { - giveConfig Config -} - -// newFakeConfigLoader creates a fake config loader -// with a fully constructed Config. -func newFakeConfigLoader(cfg Config) *fakeConfigLoader { - return &fakeConfigLoader{ - giveConfig: cfg, - } -} - -// Load implements the ConfigLoader interface. -func (f *fakeConfigLoader) Load(ctx context.Context) (Config, error) { - return f.giveConfig, nil -} diff --git a/go/vt/vttablet/tabletserver/tabletserver.go b/go/vt/vttablet/tabletserver/tabletserver.go index 8f2341ac178..dadbfd5d619 100644 --- a/go/vt/vttablet/tabletserver/tabletserver.go +++ b/go/vt/vttablet/tabletserver/tabletserver.go @@ -190,7 +190,7 @@ func NewTabletServer(ctx context.Context, env *vtenv.Environment, name string, c tsv.rt = repltracker.NewReplTracker(tsv, alias) tsv.lagThrottler = throttle.NewThrottler(tsv, srvTopoServer, topoServer, alias, tsv.rt.HeartbeatWriter(), tabletTypeFunc, throttlerPoolName) tsv.qThrottler = throttle.NewThrottler(tsv, srvTopoServer, topoServer, alias, tsv.rt.HeartbeatWriter(), tabletTypeFunc, queryThrottlerPoolName) - tsv.queryThrottler = querythrottler.NewQueryThrottler(ctx, tsv.qThrottler, querythrottler.NewFileBasedConfigLoader(), tsv) + tsv.queryThrottler = querythrottler.NewQueryThrottler(ctx, tsv.qThrottler, tsv, alias, srvTopoServer) tsv.vstreamer = vstreamer.NewEngine(tsv, srvTopoServer, tsv.se, tsv.lagThrottler, alias.Cell) tsv.tracker = schema.NewTracker(tsv, tsv.vstreamer, tsv.se) @@ -327,6 +327,8 @@ func (tsv *TabletServer) InitDBConfig(target *querypb.Target, dbcfgs *dbconfigs. tsv.lagThrottler.InitDBConfig(target.Keyspace, target.Shard) tsv.qThrottler.InitDBConfig(target.Keyspace, target.Shard) tsv.tableGC.InitDBConfig(target.Keyspace, target.Shard, dbcfgs.DBName) + tsv.queryThrottler.InitDBConfig(target.Keyspace) + return nil } diff --git a/go/vt/wrangler/testlib/planned_reparent_shard_test.go b/go/vt/wrangler/testlib/planned_reparent_shard_test.go index f160ddfa32b..23cc300e4aa 100644 --- a/go/vt/wrangler/testlib/planned_reparent_shard_test.go +++ b/go/vt/wrangler/testlib/planned_reparent_shard_test.go @@ -26,6 +26,7 @@ import ( "vitess.io/vitess/go/vt/mysqlctl" "vitess.io/vitess/go/vt/vtctl/reparentutil/policy" "vitess.io/vitess/go/vt/vtenv" + "vitess.io/vitess/go/vt/vttablet/tabletservermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,7 +37,6 @@ import ( "vitess.io/vitess/go/vt/topo/memorytopo" "vitess.io/vitess/go/vt/topo/topoproto" "vitess.io/vitess/go/vt/vtctl/reparentutil/reparenttestutil" - "vitess.io/vitess/go/vt/vttablet/tabletservermock" "vitess.io/vitess/go/vt/vttablet/tmclient" "vitess.io/vitess/go/vt/wrangler" diff --git a/proto/querythrottler.proto b/proto/querythrottler.proto new file mode 100644 index 00000000000..71dc83fa399 --- /dev/null +++ b/proto/querythrottler.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package querythrottler; + +option go_package = "vitess.io/vitess/go/vt/proto/querythrottler"; + +// ThrottlingStrategy represents the strategy used to apply throttling +enum ThrottlingStrategy { + UNKNOWN = 0; + TABLET_THROTTLER = 1; +} + +// Config defines the runtime configuration for the IncomingQueryThrottler +message Config { + bool enabled = 1; + ThrottlingStrategy strategy = 2; + TabletStrategyConfig tablet_strategy_config = 3; + bool dry_run = 4; +} + +// TabletStrategyConfig holds per-tablet-type throttling rules +message TabletStrategyConfig { + map tablet_rules = 1; +} + +// StatementRuleSet maps SQL statement types to metric rules +message StatementRuleSet { + map statement_rules = 1; +} + +// MetricRuleSet maps metric names to their throttling rules +message MetricRuleSet { + map metric_rules = 1; +} + +// MetricRule defines how to throttle based on a specific metric +message MetricRule { + repeated ThrottleThreshold thresholds = 1; +} + +// ThrottleThreshold defines a condition for throttling +message ThrottleThreshold { + double above = 1; + int32 throttle = 2; +} diff --git a/proto/topodata.proto b/proto/topodata.proto index 0103f9980fc..8f610c99026 100644 --- a/proto/topodata.proto +++ b/proto/topodata.proto @@ -28,6 +28,8 @@ package topodata; import "vtorcdata.proto"; import "vttime.proto"; +import "querythrottler.proto"; + // KeyRange describes a range of sharding keys, when range-based // sharding is used. @@ -315,6 +317,9 @@ message Keyspace { // Vtorc is the vtorc keyspace config/state for the keyspace. vtorcdata.Keyspace vtorc_state = 11; + + // QueryThrottler provides a flexible throttling configuration that supports multiple throttling strategies beyond the standard tablet throttling. + querythrottler.Config query_throttler_config = 12; } // ShardReplication describes the MySQL replication relationships @@ -447,6 +452,10 @@ message SrvKeyspace { // shards and tablets. This is copied from the global keyspace // object. ThrottlerConfig throttler_config = 6; + + // QueryThrottler provides a flexible throttling configuration that supports multiple throttling strategies beyond the standard tablet throttling. + querythrottler.Config query_throttler_config = 7; + } // CellInfo contains information about a cell. CellInfo objects are diff --git a/web/vtadmin/src/proto/vtadmin.d.ts b/web/vtadmin/src/proto/vtadmin.d.ts index d6890ece9e2..3d7db5f3430 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -18955,6 +18955,9 @@ export namespace topodata { /** Keyspace vtorc_state */ vtorc_state?: (vtorcdata.IKeyspace|null); + + /** Keyspace query_throttler_config */ + query_throttler_config?: (querythrottler.IConfig|null); } /** Represents a Keyspace. */ @@ -18987,6 +18990,9 @@ export namespace topodata { /** Keyspace vtorc_state. */ public vtorc_state?: (vtorcdata.IKeyspace|null); + /** Keyspace query_throttler_config. */ + public query_throttler_config?: (querythrottler.IConfig|null); + /** * Creates a new Keyspace instance using the specified properties. * @param [properties] Properties to set @@ -19943,6 +19949,9 @@ export namespace topodata { /** SrvKeyspace throttler_config */ throttler_config?: (topodata.IThrottlerConfig|null); + + /** SrvKeyspace query_throttler_config */ + query_throttler_config?: (querythrottler.IConfig|null); } /** Represents a SrvKeyspace. */ @@ -19960,6 +19969,9 @@ export namespace topodata { /** SrvKeyspace throttler_config. */ public throttler_config?: (topodata.IThrottlerConfig|null); + /** SrvKeyspace query_throttler_config. */ + public query_throttler_config?: (querythrottler.IConfig|null); + /** * Creates a new SrvKeyspace instance using the specified properties. * @param [properties] Properties to set @@ -20852,6 +20864,622 @@ export namespace vtorcdata { } } +/** Namespace querythrottler. */ +export namespace querythrottler { + + /** ThrottlingStrategy enum. */ + enum ThrottlingStrategy { + UNKNOWN = 0, + TABLET_THROTTLER = 1 + } + + /** Properties of a Config. */ + interface IConfig { + + /** Config enabled */ + enabled?: (boolean|null); + + /** Config strategy */ + strategy?: (querythrottler.ThrottlingStrategy|null); + + /** Config tablet_strategy_config */ + tablet_strategy_config?: (querythrottler.ITabletStrategyConfig|null); + + /** Config dry_run */ + dry_run?: (boolean|null); + } + + /** Represents a Config. */ + class Config implements IConfig { + + /** + * Constructs a new Config. + * @param [properties] Properties to set + */ + constructor(properties?: querythrottler.IConfig); + + /** Config enabled. */ + public enabled: boolean; + + /** Config strategy. */ + public strategy: querythrottler.ThrottlingStrategy; + + /** Config tablet_strategy_config. */ + public tablet_strategy_config?: (querythrottler.ITabletStrategyConfig|null); + + /** Config dry_run. */ + public dry_run: boolean; + + /** + * Creates a new Config instance using the specified properties. + * @param [properties] Properties to set + * @returns Config instance + */ + public static create(properties?: querythrottler.IConfig): querythrottler.Config; + + /** + * Encodes the specified Config message. Does not implicitly {@link querythrottler.Config.verify|verify} messages. + * @param message Config message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: querythrottler.IConfig, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified Config message, length delimited. Does not implicitly {@link querythrottler.Config.verify|verify} messages. + * @param message Config message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: querythrottler.IConfig, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Config message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns Config + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): querythrottler.Config; + + /** + * Decodes a Config message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns Config + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): querythrottler.Config; + + /** + * Verifies a Config message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a Config message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Config + */ + public static fromObject(object: { [k: string]: any }): querythrottler.Config; + + /** + * Creates a plain object from a Config message. Also converts values to other types if specified. + * @param message Config + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: querythrottler.Config, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Config to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Config + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a TabletStrategyConfig. */ + interface ITabletStrategyConfig { + + /** TabletStrategyConfig tablet_rules */ + tablet_rules?: ({ [k: string]: querythrottler.IStatementRuleSet }|null); + } + + /** Represents a TabletStrategyConfig. */ + class TabletStrategyConfig implements ITabletStrategyConfig { + + /** + * Constructs a new TabletStrategyConfig. + * @param [properties] Properties to set + */ + constructor(properties?: querythrottler.ITabletStrategyConfig); + + /** TabletStrategyConfig tablet_rules. */ + public tablet_rules: { [k: string]: querythrottler.IStatementRuleSet }; + + /** + * Creates a new TabletStrategyConfig instance using the specified properties. + * @param [properties] Properties to set + * @returns TabletStrategyConfig instance + */ + public static create(properties?: querythrottler.ITabletStrategyConfig): querythrottler.TabletStrategyConfig; + + /** + * Encodes the specified TabletStrategyConfig message. Does not implicitly {@link querythrottler.TabletStrategyConfig.verify|verify} messages. + * @param message TabletStrategyConfig message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: querythrottler.ITabletStrategyConfig, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified TabletStrategyConfig message, length delimited. Does not implicitly {@link querythrottler.TabletStrategyConfig.verify|verify} messages. + * @param message TabletStrategyConfig message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: querythrottler.ITabletStrategyConfig, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a TabletStrategyConfig message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns TabletStrategyConfig + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): querythrottler.TabletStrategyConfig; + + /** + * Decodes a TabletStrategyConfig message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns TabletStrategyConfig + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): querythrottler.TabletStrategyConfig; + + /** + * Verifies a TabletStrategyConfig message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a TabletStrategyConfig message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns TabletStrategyConfig + */ + public static fromObject(object: { [k: string]: any }): querythrottler.TabletStrategyConfig; + + /** + * Creates a plain object from a TabletStrategyConfig message. Also converts values to other types if specified. + * @param message TabletStrategyConfig + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: querythrottler.TabletStrategyConfig, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this TabletStrategyConfig to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for TabletStrategyConfig + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a StatementRuleSet. */ + interface IStatementRuleSet { + + /** StatementRuleSet statement_rules */ + statement_rules?: ({ [k: string]: querythrottler.IMetricRuleSet }|null); + } + + /** Represents a StatementRuleSet. */ + class StatementRuleSet implements IStatementRuleSet { + + /** + * Constructs a new StatementRuleSet. + * @param [properties] Properties to set + */ + constructor(properties?: querythrottler.IStatementRuleSet); + + /** StatementRuleSet statement_rules. */ + public statement_rules: { [k: string]: querythrottler.IMetricRuleSet }; + + /** + * Creates a new StatementRuleSet instance using the specified properties. + * @param [properties] Properties to set + * @returns StatementRuleSet instance + */ + public static create(properties?: querythrottler.IStatementRuleSet): querythrottler.StatementRuleSet; + + /** + * Encodes the specified StatementRuleSet message. Does not implicitly {@link querythrottler.StatementRuleSet.verify|verify} messages. + * @param message StatementRuleSet message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: querythrottler.IStatementRuleSet, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified StatementRuleSet message, length delimited. Does not implicitly {@link querythrottler.StatementRuleSet.verify|verify} messages. + * @param message StatementRuleSet message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: querythrottler.IStatementRuleSet, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a StatementRuleSet message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns StatementRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): querythrottler.StatementRuleSet; + + /** + * Decodes a StatementRuleSet message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns StatementRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): querythrottler.StatementRuleSet; + + /** + * Verifies a StatementRuleSet message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a StatementRuleSet message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns StatementRuleSet + */ + public static fromObject(object: { [k: string]: any }): querythrottler.StatementRuleSet; + + /** + * Creates a plain object from a StatementRuleSet message. Also converts values to other types if specified. + * @param message StatementRuleSet + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: querythrottler.StatementRuleSet, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this StatementRuleSet to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for StatementRuleSet + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a MetricRuleSet. */ + interface IMetricRuleSet { + + /** MetricRuleSet metric_rules */ + metric_rules?: ({ [k: string]: querythrottler.IMetricRule }|null); + } + + /** Represents a MetricRuleSet. */ + class MetricRuleSet implements IMetricRuleSet { + + /** + * Constructs a new MetricRuleSet. + * @param [properties] Properties to set + */ + constructor(properties?: querythrottler.IMetricRuleSet); + + /** MetricRuleSet metric_rules. */ + public metric_rules: { [k: string]: querythrottler.IMetricRule }; + + /** + * Creates a new MetricRuleSet instance using the specified properties. + * @param [properties] Properties to set + * @returns MetricRuleSet instance + */ + public static create(properties?: querythrottler.IMetricRuleSet): querythrottler.MetricRuleSet; + + /** + * Encodes the specified MetricRuleSet message. Does not implicitly {@link querythrottler.MetricRuleSet.verify|verify} messages. + * @param message MetricRuleSet message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: querythrottler.IMetricRuleSet, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified MetricRuleSet message, length delimited. Does not implicitly {@link querythrottler.MetricRuleSet.verify|verify} messages. + * @param message MetricRuleSet message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: querythrottler.IMetricRuleSet, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a MetricRuleSet message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns MetricRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): querythrottler.MetricRuleSet; + + /** + * Decodes a MetricRuleSet message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns MetricRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): querythrottler.MetricRuleSet; + + /** + * Verifies a MetricRuleSet message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a MetricRuleSet message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns MetricRuleSet + */ + public static fromObject(object: { [k: string]: any }): querythrottler.MetricRuleSet; + + /** + * Creates a plain object from a MetricRuleSet message. Also converts values to other types if specified. + * @param message MetricRuleSet + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: querythrottler.MetricRuleSet, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this MetricRuleSet to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for MetricRuleSet + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a MetricRule. */ + interface IMetricRule { + + /** MetricRule thresholds */ + thresholds?: (querythrottler.IThrottleThreshold[]|null); + } + + /** Represents a MetricRule. */ + class MetricRule implements IMetricRule { + + /** + * Constructs a new MetricRule. + * @param [properties] Properties to set + */ + constructor(properties?: querythrottler.IMetricRule); + + /** MetricRule thresholds. */ + public thresholds: querythrottler.IThrottleThreshold[]; + + /** + * Creates a new MetricRule instance using the specified properties. + * @param [properties] Properties to set + * @returns MetricRule instance + */ + public static create(properties?: querythrottler.IMetricRule): querythrottler.MetricRule; + + /** + * Encodes the specified MetricRule message. Does not implicitly {@link querythrottler.MetricRule.verify|verify} messages. + * @param message MetricRule message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: querythrottler.IMetricRule, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified MetricRule message, length delimited. Does not implicitly {@link querythrottler.MetricRule.verify|verify} messages. + * @param message MetricRule message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: querythrottler.IMetricRule, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a MetricRule message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns MetricRule + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): querythrottler.MetricRule; + + /** + * Decodes a MetricRule message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns MetricRule + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): querythrottler.MetricRule; + + /** + * Verifies a MetricRule message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a MetricRule message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns MetricRule + */ + public static fromObject(object: { [k: string]: any }): querythrottler.MetricRule; + + /** + * Creates a plain object from a MetricRule message. Also converts values to other types if specified. + * @param message MetricRule + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: querythrottler.MetricRule, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this MetricRule to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for MetricRule + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a ThrottleThreshold. */ + interface IThrottleThreshold { + + /** ThrottleThreshold above */ + above?: (number|null); + + /** ThrottleThreshold throttle */ + throttle?: (number|null); + } + + /** Represents a ThrottleThreshold. */ + class ThrottleThreshold implements IThrottleThreshold { + + /** + * Constructs a new ThrottleThreshold. + * @param [properties] Properties to set + */ + constructor(properties?: querythrottler.IThrottleThreshold); + + /** ThrottleThreshold above. */ + public above: number; + + /** ThrottleThreshold throttle. */ + public throttle: number; + + /** + * Creates a new ThrottleThreshold instance using the specified properties. + * @param [properties] Properties to set + * @returns ThrottleThreshold instance + */ + public static create(properties?: querythrottler.IThrottleThreshold): querythrottler.ThrottleThreshold; + + /** + * Encodes the specified ThrottleThreshold message. Does not implicitly {@link querythrottler.ThrottleThreshold.verify|verify} messages. + * @param message ThrottleThreshold message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: querythrottler.IThrottleThreshold, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified ThrottleThreshold message, length delimited. Does not implicitly {@link querythrottler.ThrottleThreshold.verify|verify} messages. + * @param message ThrottleThreshold message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: querythrottler.IThrottleThreshold, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a ThrottleThreshold message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns ThrottleThreshold + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): querythrottler.ThrottleThreshold; + + /** + * Decodes a ThrottleThreshold message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns ThrottleThreshold + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): querythrottler.ThrottleThreshold; + + /** + * Verifies a ThrottleThreshold message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a ThrottleThreshold message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ThrottleThreshold + */ + public static fromObject(object: { [k: string]: any }): querythrottler.ThrottleThreshold; + + /** + * Creates a plain object from a ThrottleThreshold message. Also converts values to other types if specified. + * @param message ThrottleThreshold + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: querythrottler.ThrottleThreshold, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ThrottleThreshold to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ThrottleThreshold + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } +} + /** Namespace vtrpc. */ export namespace vtrpc { diff --git a/web/vtadmin/src/proto/vtadmin.js b/web/vtadmin/src/proto/vtadmin.js index 40b8c6ff4e0..df1cdc0f375 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -43552,6 +43552,7 @@ export const topodata = $root.topodata = (() => { * @property {topodata.IThrottlerConfig|null} [throttler_config] Keyspace throttler_config * @property {string|null} [sidecar_db_name] Keyspace sidecar_db_name * @property {vtorcdata.IKeyspace|null} [vtorc_state] Keyspace vtorc_state + * @property {querythrottler.IConfig|null} [query_throttler_config] Keyspace query_throttler_config */ /** @@ -43625,6 +43626,14 @@ export const topodata = $root.topodata = (() => { */ Keyspace.prototype.vtorc_state = null; + /** + * Keyspace query_throttler_config. + * @member {querythrottler.IConfig|null|undefined} query_throttler_config + * @memberof topodata.Keyspace + * @instance + */ + Keyspace.prototype.query_throttler_config = null; + /** * Creates a new Keyspace instance using the specified properties. * @function create @@ -43663,6 +43672,8 @@ export const topodata = $root.topodata = (() => { writer.uint32(/* id 10, wireType 2 =*/82).string(message.sidecar_db_name); if (message.vtorc_state != null && Object.hasOwnProperty.call(message, "vtorc_state")) $root.vtorcdata.Keyspace.encode(message.vtorc_state, writer.uint32(/* id 11, wireType 2 =*/90).fork()).ldelim(); + if (message.query_throttler_config != null && Object.hasOwnProperty.call(message, "query_throttler_config")) + $root.querythrottler.Config.encode(message.query_throttler_config, writer.uint32(/* id 12, wireType 2 =*/98).fork()).ldelim(); return writer; }; @@ -43725,6 +43736,10 @@ export const topodata = $root.topodata = (() => { message.vtorc_state = $root.vtorcdata.Keyspace.decode(reader, reader.uint32()); break; } + case 12: { + message.query_throttler_config = $root.querythrottler.Config.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -43792,6 +43807,11 @@ export const topodata = $root.topodata = (() => { if (error) return "vtorc_state." + error; } + if (message.query_throttler_config != null && message.hasOwnProperty("query_throttler_config")) { + let error = $root.querythrottler.Config.verify(message.query_throttler_config); + if (error) + return "query_throttler_config." + error; + } return null; }; @@ -43844,6 +43864,11 @@ export const topodata = $root.topodata = (() => { throw TypeError(".topodata.Keyspace.vtorc_state: object expected"); message.vtorc_state = $root.vtorcdata.Keyspace.fromObject(object.vtorc_state); } + if (object.query_throttler_config != null) { + if (typeof object.query_throttler_config !== "object") + throw TypeError(".topodata.Keyspace.query_throttler_config: object expected"); + message.query_throttler_config = $root.querythrottler.Config.fromObject(object.query_throttler_config); + } return message; }; @@ -43868,6 +43893,7 @@ export const topodata = $root.topodata = (() => { object.throttler_config = null; object.sidecar_db_name = ""; object.vtorc_state = null; + object.query_throttler_config = null; } if (message.keyspace_type != null && message.hasOwnProperty("keyspace_type")) object.keyspace_type = options.enums === String ? $root.topodata.KeyspaceType[message.keyspace_type] === undefined ? message.keyspace_type : $root.topodata.KeyspaceType[message.keyspace_type] : message.keyspace_type; @@ -43883,6 +43909,8 @@ export const topodata = $root.topodata = (() => { object.sidecar_db_name = message.sidecar_db_name; if (message.vtorc_state != null && message.hasOwnProperty("vtorc_state")) object.vtorc_state = $root.vtorcdata.Keyspace.toObject(message.vtorc_state, options); + if (message.query_throttler_config != null && message.hasOwnProperty("query_throttler_config")) + object.query_throttler_config = $root.querythrottler.Config.toObject(message.query_throttler_config, options); return object; }; @@ -46072,6 +46100,7 @@ export const topodata = $root.topodata = (() => { * @interface ISrvKeyspace * @property {Array.|null} [partitions] SrvKeyspace partitions * @property {topodata.IThrottlerConfig|null} [throttler_config] SrvKeyspace throttler_config + * @property {querythrottler.IConfig|null} [query_throttler_config] SrvKeyspace query_throttler_config */ /** @@ -46106,6 +46135,14 @@ export const topodata = $root.topodata = (() => { */ SrvKeyspace.prototype.throttler_config = null; + /** + * SrvKeyspace query_throttler_config. + * @member {querythrottler.IConfig|null|undefined} query_throttler_config + * @memberof topodata.SrvKeyspace + * @instance + */ + SrvKeyspace.prototype.query_throttler_config = null; + /** * Creates a new SrvKeyspace instance using the specified properties. * @function create @@ -46135,6 +46172,8 @@ export const topodata = $root.topodata = (() => { $root.topodata.SrvKeyspace.KeyspacePartition.encode(message.partitions[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); if (message.throttler_config != null && Object.hasOwnProperty.call(message, "throttler_config")) $root.topodata.ThrottlerConfig.encode(message.throttler_config, writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); + if (message.query_throttler_config != null && Object.hasOwnProperty.call(message, "query_throttler_config")) + $root.querythrottler.Config.encode(message.query_throttler_config, writer.uint32(/* id 7, wireType 2 =*/58).fork()).ldelim(); return writer; }; @@ -46179,6 +46218,10 @@ export const topodata = $root.topodata = (() => { message.throttler_config = $root.topodata.ThrottlerConfig.decode(reader, reader.uint32()); break; } + case 7: { + message.query_throttler_config = $root.querythrottler.Config.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -46228,6 +46271,11 @@ export const topodata = $root.topodata = (() => { if (error) return "throttler_config." + error; } + if (message.query_throttler_config != null && message.hasOwnProperty("query_throttler_config")) { + let error = $root.querythrottler.Config.verify(message.query_throttler_config); + if (error) + return "query_throttler_config." + error; + } return null; }; @@ -46258,6 +46306,11 @@ export const topodata = $root.topodata = (() => { throw TypeError(".topodata.SrvKeyspace.throttler_config: object expected"); message.throttler_config = $root.topodata.ThrottlerConfig.fromObject(object.throttler_config); } + if (object.query_throttler_config != null) { + if (typeof object.query_throttler_config !== "object") + throw TypeError(".topodata.SrvKeyspace.query_throttler_config: object expected"); + message.query_throttler_config = $root.querythrottler.Config.fromObject(object.query_throttler_config); + } return message; }; @@ -46276,8 +46329,10 @@ export const topodata = $root.topodata = (() => { let object = {}; if (options.arrays || options.defaults) object.partitions = []; - if (options.defaults) + if (options.defaults) { object.throttler_config = null; + object.query_throttler_config = null; + } if (message.partitions && message.partitions.length) { object.partitions = []; for (let j = 0; j < message.partitions.length; ++j) @@ -46285,6 +46340,8 @@ export const topodata = $root.topodata = (() => { } if (message.throttler_config != null && message.hasOwnProperty("throttler_config")) object.throttler_config = $root.topodata.ThrottlerConfig.toObject(message.throttler_config, options); + if (message.query_throttler_config != null && message.hasOwnProperty("query_throttler_config")) + object.query_throttler_config = $root.querythrottler.Config.toObject(message.query_throttler_config, options); return object; }; @@ -48223,6 +48280,1515 @@ export const vtorcdata = $root.vtorcdata = (() => { return vtorcdata; })(); +export const querythrottler = $root.querythrottler = (() => { + + /** + * Namespace querythrottler. + * @exports querythrottler + * @namespace + */ + const querythrottler = {}; + + /** + * ThrottlingStrategy enum. + * @name querythrottler.ThrottlingStrategy + * @enum {number} + * @property {number} UNKNOWN=0 UNKNOWN value + * @property {number} TABLET_THROTTLER=1 TABLET_THROTTLER value + */ + querythrottler.ThrottlingStrategy = (function() { + const valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "UNKNOWN"] = 0; + values[valuesById[1] = "TABLET_THROTTLER"] = 1; + return values; + })(); + + querythrottler.Config = (function() { + + /** + * Properties of a Config. + * @memberof querythrottler + * @interface IConfig + * @property {boolean|null} [enabled] Config enabled + * @property {querythrottler.ThrottlingStrategy|null} [strategy] Config strategy + * @property {querythrottler.ITabletStrategyConfig|null} [tablet_strategy_config] Config tablet_strategy_config + * @property {boolean|null} [dry_run] Config dry_run + */ + + /** + * Constructs a new Config. + * @memberof querythrottler + * @classdesc Represents a Config. + * @implements IConfig + * @constructor + * @param {querythrottler.IConfig=} [properties] Properties to set + */ + function Config(properties) { + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Config enabled. + * @member {boolean} enabled + * @memberof querythrottler.Config + * @instance + */ + Config.prototype.enabled = false; + + /** + * Config strategy. + * @member {querythrottler.ThrottlingStrategy} strategy + * @memberof querythrottler.Config + * @instance + */ + Config.prototype.strategy = 0; + + /** + * Config tablet_strategy_config. + * @member {querythrottler.ITabletStrategyConfig|null|undefined} tablet_strategy_config + * @memberof querythrottler.Config + * @instance + */ + Config.prototype.tablet_strategy_config = null; + + /** + * Config dry_run. + * @member {boolean} dry_run + * @memberof querythrottler.Config + * @instance + */ + Config.prototype.dry_run = false; + + /** + * Creates a new Config instance using the specified properties. + * @function create + * @memberof querythrottler.Config + * @static + * @param {querythrottler.IConfig=} [properties] Properties to set + * @returns {querythrottler.Config} Config instance + */ + Config.create = function create(properties) { + return new Config(properties); + }; + + /** + * Encodes the specified Config message. Does not implicitly {@link querythrottler.Config.verify|verify} messages. + * @function encode + * @memberof querythrottler.Config + * @static + * @param {querythrottler.IConfig} message Config message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Config.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.enabled != null && Object.hasOwnProperty.call(message, "enabled")) + writer.uint32(/* id 1, wireType 0 =*/8).bool(message.enabled); + if (message.strategy != null && Object.hasOwnProperty.call(message, "strategy")) + writer.uint32(/* id 2, wireType 0 =*/16).int32(message.strategy); + if (message.tablet_strategy_config != null && Object.hasOwnProperty.call(message, "tablet_strategy_config")) + $root.querythrottler.TabletStrategyConfig.encode(message.tablet_strategy_config, writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); + if (message.dry_run != null && Object.hasOwnProperty.call(message, "dry_run")) + writer.uint32(/* id 4, wireType 0 =*/32).bool(message.dry_run); + return writer; + }; + + /** + * Encodes the specified Config message, length delimited. Does not implicitly {@link querythrottler.Config.verify|verify} messages. + * @function encodeDelimited + * @memberof querythrottler.Config + * @static + * @param {querythrottler.IConfig} message Config message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Config.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Config message from the specified reader or buffer. + * @function decode + * @memberof querythrottler.Config + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {querythrottler.Config} Config + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Config.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.querythrottler.Config(); + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.enabled = reader.bool(); + break; + } + case 2: { + message.strategy = reader.int32(); + break; + } + case 3: { + message.tablet_strategy_config = $root.querythrottler.TabletStrategyConfig.decode(reader, reader.uint32()); + break; + } + case 4: { + message.dry_run = reader.bool(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Config message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof querythrottler.Config + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {querythrottler.Config} Config + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Config.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Config message. + * @function verify + * @memberof querythrottler.Config + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Config.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.enabled != null && message.hasOwnProperty("enabled")) + if (typeof message.enabled !== "boolean") + return "enabled: boolean expected"; + if (message.strategy != null && message.hasOwnProperty("strategy")) + switch (message.strategy) { + default: + return "strategy: enum value expected"; + case 0: + case 1: + break; + } + if (message.tablet_strategy_config != null && message.hasOwnProperty("tablet_strategy_config")) { + let error = $root.querythrottler.TabletStrategyConfig.verify(message.tablet_strategy_config); + if (error) + return "tablet_strategy_config." + error; + } + if (message.dry_run != null && message.hasOwnProperty("dry_run")) + if (typeof message.dry_run !== "boolean") + return "dry_run: boolean expected"; + return null; + }; + + /** + * Creates a Config message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof querythrottler.Config + * @static + * @param {Object.} object Plain object + * @returns {querythrottler.Config} Config + */ + Config.fromObject = function fromObject(object) { + if (object instanceof $root.querythrottler.Config) + return object; + let message = new $root.querythrottler.Config(); + if (object.enabled != null) + message.enabled = Boolean(object.enabled); + switch (object.strategy) { + default: + if (typeof object.strategy === "number") { + message.strategy = object.strategy; + break; + } + break; + case "UNKNOWN": + case 0: + message.strategy = 0; + break; + case "TABLET_THROTTLER": + case 1: + message.strategy = 1; + break; + } + if (object.tablet_strategy_config != null) { + if (typeof object.tablet_strategy_config !== "object") + throw TypeError(".querythrottler.Config.tablet_strategy_config: object expected"); + message.tablet_strategy_config = $root.querythrottler.TabletStrategyConfig.fromObject(object.tablet_strategy_config); + } + if (object.dry_run != null) + message.dry_run = Boolean(object.dry_run); + return message; + }; + + /** + * Creates a plain object from a Config message. Also converts values to other types if specified. + * @function toObject + * @memberof querythrottler.Config + * @static + * @param {querythrottler.Config} message Config + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Config.toObject = function toObject(message, options) { + if (!options) + options = {}; + let object = {}; + if (options.defaults) { + object.enabled = false; + object.strategy = options.enums === String ? "UNKNOWN" : 0; + object.tablet_strategy_config = null; + object.dry_run = false; + } + if (message.enabled != null && message.hasOwnProperty("enabled")) + object.enabled = message.enabled; + if (message.strategy != null && message.hasOwnProperty("strategy")) + object.strategy = options.enums === String ? $root.querythrottler.ThrottlingStrategy[message.strategy] === undefined ? message.strategy : $root.querythrottler.ThrottlingStrategy[message.strategy] : message.strategy; + if (message.tablet_strategy_config != null && message.hasOwnProperty("tablet_strategy_config")) + object.tablet_strategy_config = $root.querythrottler.TabletStrategyConfig.toObject(message.tablet_strategy_config, options); + if (message.dry_run != null && message.hasOwnProperty("dry_run")) + object.dry_run = message.dry_run; + return object; + }; + + /** + * Converts this Config to JSON. + * @function toJSON + * @memberof querythrottler.Config + * @instance + * @returns {Object.} JSON object + */ + Config.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Config + * @function getTypeUrl + * @memberof querythrottler.Config + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Config.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/querythrottler.Config"; + }; + + return Config; + })(); + + querythrottler.TabletStrategyConfig = (function() { + + /** + * Properties of a TabletStrategyConfig. + * @memberof querythrottler + * @interface ITabletStrategyConfig + * @property {Object.|null} [tablet_rules] TabletStrategyConfig tablet_rules + */ + + /** + * Constructs a new TabletStrategyConfig. + * @memberof querythrottler + * @classdesc Represents a TabletStrategyConfig. + * @implements ITabletStrategyConfig + * @constructor + * @param {querythrottler.ITabletStrategyConfig=} [properties] Properties to set + */ + function TabletStrategyConfig(properties) { + this.tablet_rules = {}; + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * TabletStrategyConfig tablet_rules. + * @member {Object.} tablet_rules + * @memberof querythrottler.TabletStrategyConfig + * @instance + */ + TabletStrategyConfig.prototype.tablet_rules = $util.emptyObject; + + /** + * Creates a new TabletStrategyConfig instance using the specified properties. + * @function create + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {querythrottler.ITabletStrategyConfig=} [properties] Properties to set + * @returns {querythrottler.TabletStrategyConfig} TabletStrategyConfig instance + */ + TabletStrategyConfig.create = function create(properties) { + return new TabletStrategyConfig(properties); + }; + + /** + * Encodes the specified TabletStrategyConfig message. Does not implicitly {@link querythrottler.TabletStrategyConfig.verify|verify} messages. + * @function encode + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {querythrottler.ITabletStrategyConfig} message TabletStrategyConfig message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + TabletStrategyConfig.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.tablet_rules != null && Object.hasOwnProperty.call(message, "tablet_rules")) + for (let keys = Object.keys(message.tablet_rules), i = 0; i < keys.length; ++i) { + writer.uint32(/* id 1, wireType 2 =*/10).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]); + $root.querythrottler.StatementRuleSet.encode(message.tablet_rules[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); + } + return writer; + }; + + /** + * Encodes the specified TabletStrategyConfig message, length delimited. Does not implicitly {@link querythrottler.TabletStrategyConfig.verify|verify} messages. + * @function encodeDelimited + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {querythrottler.ITabletStrategyConfig} message TabletStrategyConfig message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + TabletStrategyConfig.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a TabletStrategyConfig message from the specified reader or buffer. + * @function decode + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {querythrottler.TabletStrategyConfig} TabletStrategyConfig + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + TabletStrategyConfig.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.querythrottler.TabletStrategyConfig(), key, value; + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (message.tablet_rules === $util.emptyObject) + message.tablet_rules = {}; + let end2 = reader.uint32() + reader.pos; + key = ""; + value = null; + while (reader.pos < end2) { + let tag2 = reader.uint32(); + switch (tag2 >>> 3) { + case 1: + key = reader.string(); + break; + case 2: + value = $root.querythrottler.StatementRuleSet.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag2 & 7); + break; + } + } + message.tablet_rules[key] = value; + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a TabletStrategyConfig message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {querythrottler.TabletStrategyConfig} TabletStrategyConfig + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + TabletStrategyConfig.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a TabletStrategyConfig message. + * @function verify + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + TabletStrategyConfig.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.tablet_rules != null && message.hasOwnProperty("tablet_rules")) { + if (!$util.isObject(message.tablet_rules)) + return "tablet_rules: object expected"; + let key = Object.keys(message.tablet_rules); + for (let i = 0; i < key.length; ++i) { + let error = $root.querythrottler.StatementRuleSet.verify(message.tablet_rules[key[i]]); + if (error) + return "tablet_rules." + error; + } + } + return null; + }; + + /** + * Creates a TabletStrategyConfig message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {Object.} object Plain object + * @returns {querythrottler.TabletStrategyConfig} TabletStrategyConfig + */ + TabletStrategyConfig.fromObject = function fromObject(object) { + if (object instanceof $root.querythrottler.TabletStrategyConfig) + return object; + let message = new $root.querythrottler.TabletStrategyConfig(); + if (object.tablet_rules) { + if (typeof object.tablet_rules !== "object") + throw TypeError(".querythrottler.TabletStrategyConfig.tablet_rules: object expected"); + message.tablet_rules = {}; + for (let keys = Object.keys(object.tablet_rules), i = 0; i < keys.length; ++i) { + if (typeof object.tablet_rules[keys[i]] !== "object") + throw TypeError(".querythrottler.TabletStrategyConfig.tablet_rules: object expected"); + message.tablet_rules[keys[i]] = $root.querythrottler.StatementRuleSet.fromObject(object.tablet_rules[keys[i]]); + } + } + return message; + }; + + /** + * Creates a plain object from a TabletStrategyConfig message. Also converts values to other types if specified. + * @function toObject + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {querythrottler.TabletStrategyConfig} message TabletStrategyConfig + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + TabletStrategyConfig.toObject = function toObject(message, options) { + if (!options) + options = {}; + let object = {}; + if (options.objects || options.defaults) + object.tablet_rules = {}; + let keys2; + if (message.tablet_rules && (keys2 = Object.keys(message.tablet_rules)).length) { + object.tablet_rules = {}; + for (let j = 0; j < keys2.length; ++j) + object.tablet_rules[keys2[j]] = $root.querythrottler.StatementRuleSet.toObject(message.tablet_rules[keys2[j]], options); + } + return object; + }; + + /** + * Converts this TabletStrategyConfig to JSON. + * @function toJSON + * @memberof querythrottler.TabletStrategyConfig + * @instance + * @returns {Object.} JSON object + */ + TabletStrategyConfig.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for TabletStrategyConfig + * @function getTypeUrl + * @memberof querythrottler.TabletStrategyConfig + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + TabletStrategyConfig.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/querythrottler.TabletStrategyConfig"; + }; + + return TabletStrategyConfig; + })(); + + querythrottler.StatementRuleSet = (function() { + + /** + * Properties of a StatementRuleSet. + * @memberof querythrottler + * @interface IStatementRuleSet + * @property {Object.|null} [statement_rules] StatementRuleSet statement_rules + */ + + /** + * Constructs a new StatementRuleSet. + * @memberof querythrottler + * @classdesc Represents a StatementRuleSet. + * @implements IStatementRuleSet + * @constructor + * @param {querythrottler.IStatementRuleSet=} [properties] Properties to set + */ + function StatementRuleSet(properties) { + this.statement_rules = {}; + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * StatementRuleSet statement_rules. + * @member {Object.} statement_rules + * @memberof querythrottler.StatementRuleSet + * @instance + */ + StatementRuleSet.prototype.statement_rules = $util.emptyObject; + + /** + * Creates a new StatementRuleSet instance using the specified properties. + * @function create + * @memberof querythrottler.StatementRuleSet + * @static + * @param {querythrottler.IStatementRuleSet=} [properties] Properties to set + * @returns {querythrottler.StatementRuleSet} StatementRuleSet instance + */ + StatementRuleSet.create = function create(properties) { + return new StatementRuleSet(properties); + }; + + /** + * Encodes the specified StatementRuleSet message. Does not implicitly {@link querythrottler.StatementRuleSet.verify|verify} messages. + * @function encode + * @memberof querythrottler.StatementRuleSet + * @static + * @param {querythrottler.IStatementRuleSet} message StatementRuleSet message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + StatementRuleSet.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.statement_rules != null && Object.hasOwnProperty.call(message, "statement_rules")) + for (let keys = Object.keys(message.statement_rules), i = 0; i < keys.length; ++i) { + writer.uint32(/* id 1, wireType 2 =*/10).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]); + $root.querythrottler.MetricRuleSet.encode(message.statement_rules[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); + } + return writer; + }; + + /** + * Encodes the specified StatementRuleSet message, length delimited. Does not implicitly {@link querythrottler.StatementRuleSet.verify|verify} messages. + * @function encodeDelimited + * @memberof querythrottler.StatementRuleSet + * @static + * @param {querythrottler.IStatementRuleSet} message StatementRuleSet message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + StatementRuleSet.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a StatementRuleSet message from the specified reader or buffer. + * @function decode + * @memberof querythrottler.StatementRuleSet + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {querythrottler.StatementRuleSet} StatementRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + StatementRuleSet.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.querythrottler.StatementRuleSet(), key, value; + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (message.statement_rules === $util.emptyObject) + message.statement_rules = {}; + let end2 = reader.uint32() + reader.pos; + key = ""; + value = null; + while (reader.pos < end2) { + let tag2 = reader.uint32(); + switch (tag2 >>> 3) { + case 1: + key = reader.string(); + break; + case 2: + value = $root.querythrottler.MetricRuleSet.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag2 & 7); + break; + } + } + message.statement_rules[key] = value; + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a StatementRuleSet message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof querythrottler.StatementRuleSet + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {querythrottler.StatementRuleSet} StatementRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + StatementRuleSet.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a StatementRuleSet message. + * @function verify + * @memberof querythrottler.StatementRuleSet + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + StatementRuleSet.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.statement_rules != null && message.hasOwnProperty("statement_rules")) { + if (!$util.isObject(message.statement_rules)) + return "statement_rules: object expected"; + let key = Object.keys(message.statement_rules); + for (let i = 0; i < key.length; ++i) { + let error = $root.querythrottler.MetricRuleSet.verify(message.statement_rules[key[i]]); + if (error) + return "statement_rules." + error; + } + } + return null; + }; + + /** + * Creates a StatementRuleSet message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof querythrottler.StatementRuleSet + * @static + * @param {Object.} object Plain object + * @returns {querythrottler.StatementRuleSet} StatementRuleSet + */ + StatementRuleSet.fromObject = function fromObject(object) { + if (object instanceof $root.querythrottler.StatementRuleSet) + return object; + let message = new $root.querythrottler.StatementRuleSet(); + if (object.statement_rules) { + if (typeof object.statement_rules !== "object") + throw TypeError(".querythrottler.StatementRuleSet.statement_rules: object expected"); + message.statement_rules = {}; + for (let keys = Object.keys(object.statement_rules), i = 0; i < keys.length; ++i) { + if (typeof object.statement_rules[keys[i]] !== "object") + throw TypeError(".querythrottler.StatementRuleSet.statement_rules: object expected"); + message.statement_rules[keys[i]] = $root.querythrottler.MetricRuleSet.fromObject(object.statement_rules[keys[i]]); + } + } + return message; + }; + + /** + * Creates a plain object from a StatementRuleSet message. Also converts values to other types if specified. + * @function toObject + * @memberof querythrottler.StatementRuleSet + * @static + * @param {querythrottler.StatementRuleSet} message StatementRuleSet + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + StatementRuleSet.toObject = function toObject(message, options) { + if (!options) + options = {}; + let object = {}; + if (options.objects || options.defaults) + object.statement_rules = {}; + let keys2; + if (message.statement_rules && (keys2 = Object.keys(message.statement_rules)).length) { + object.statement_rules = {}; + for (let j = 0; j < keys2.length; ++j) + object.statement_rules[keys2[j]] = $root.querythrottler.MetricRuleSet.toObject(message.statement_rules[keys2[j]], options); + } + return object; + }; + + /** + * Converts this StatementRuleSet to JSON. + * @function toJSON + * @memberof querythrottler.StatementRuleSet + * @instance + * @returns {Object.} JSON object + */ + StatementRuleSet.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for StatementRuleSet + * @function getTypeUrl + * @memberof querythrottler.StatementRuleSet + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + StatementRuleSet.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/querythrottler.StatementRuleSet"; + }; + + return StatementRuleSet; + })(); + + querythrottler.MetricRuleSet = (function() { + + /** + * Properties of a MetricRuleSet. + * @memberof querythrottler + * @interface IMetricRuleSet + * @property {Object.|null} [metric_rules] MetricRuleSet metric_rules + */ + + /** + * Constructs a new MetricRuleSet. + * @memberof querythrottler + * @classdesc Represents a MetricRuleSet. + * @implements IMetricRuleSet + * @constructor + * @param {querythrottler.IMetricRuleSet=} [properties] Properties to set + */ + function MetricRuleSet(properties) { + this.metric_rules = {}; + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * MetricRuleSet metric_rules. + * @member {Object.} metric_rules + * @memberof querythrottler.MetricRuleSet + * @instance + */ + MetricRuleSet.prototype.metric_rules = $util.emptyObject; + + /** + * Creates a new MetricRuleSet instance using the specified properties. + * @function create + * @memberof querythrottler.MetricRuleSet + * @static + * @param {querythrottler.IMetricRuleSet=} [properties] Properties to set + * @returns {querythrottler.MetricRuleSet} MetricRuleSet instance + */ + MetricRuleSet.create = function create(properties) { + return new MetricRuleSet(properties); + }; + + /** + * Encodes the specified MetricRuleSet message. Does not implicitly {@link querythrottler.MetricRuleSet.verify|verify} messages. + * @function encode + * @memberof querythrottler.MetricRuleSet + * @static + * @param {querythrottler.IMetricRuleSet} message MetricRuleSet message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MetricRuleSet.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.metric_rules != null && Object.hasOwnProperty.call(message, "metric_rules")) + for (let keys = Object.keys(message.metric_rules), i = 0; i < keys.length; ++i) { + writer.uint32(/* id 1, wireType 2 =*/10).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]); + $root.querythrottler.MetricRule.encode(message.metric_rules[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); + } + return writer; + }; + + /** + * Encodes the specified MetricRuleSet message, length delimited. Does not implicitly {@link querythrottler.MetricRuleSet.verify|verify} messages. + * @function encodeDelimited + * @memberof querythrottler.MetricRuleSet + * @static + * @param {querythrottler.IMetricRuleSet} message MetricRuleSet message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MetricRuleSet.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MetricRuleSet message from the specified reader or buffer. + * @function decode + * @memberof querythrottler.MetricRuleSet + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {querythrottler.MetricRuleSet} MetricRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MetricRuleSet.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.querythrottler.MetricRuleSet(), key, value; + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (message.metric_rules === $util.emptyObject) + message.metric_rules = {}; + let end2 = reader.uint32() + reader.pos; + key = ""; + value = null; + while (reader.pos < end2) { + let tag2 = reader.uint32(); + switch (tag2 >>> 3) { + case 1: + key = reader.string(); + break; + case 2: + value = $root.querythrottler.MetricRule.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag2 & 7); + break; + } + } + message.metric_rules[key] = value; + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MetricRuleSet message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof querythrottler.MetricRuleSet + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {querythrottler.MetricRuleSet} MetricRuleSet + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MetricRuleSet.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MetricRuleSet message. + * @function verify + * @memberof querythrottler.MetricRuleSet + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MetricRuleSet.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.metric_rules != null && message.hasOwnProperty("metric_rules")) { + if (!$util.isObject(message.metric_rules)) + return "metric_rules: object expected"; + let key = Object.keys(message.metric_rules); + for (let i = 0; i < key.length; ++i) { + let error = $root.querythrottler.MetricRule.verify(message.metric_rules[key[i]]); + if (error) + return "metric_rules." + error; + } + } + return null; + }; + + /** + * Creates a MetricRuleSet message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof querythrottler.MetricRuleSet + * @static + * @param {Object.} object Plain object + * @returns {querythrottler.MetricRuleSet} MetricRuleSet + */ + MetricRuleSet.fromObject = function fromObject(object) { + if (object instanceof $root.querythrottler.MetricRuleSet) + return object; + let message = new $root.querythrottler.MetricRuleSet(); + if (object.metric_rules) { + if (typeof object.metric_rules !== "object") + throw TypeError(".querythrottler.MetricRuleSet.metric_rules: object expected"); + message.metric_rules = {}; + for (let keys = Object.keys(object.metric_rules), i = 0; i < keys.length; ++i) { + if (typeof object.metric_rules[keys[i]] !== "object") + throw TypeError(".querythrottler.MetricRuleSet.metric_rules: object expected"); + message.metric_rules[keys[i]] = $root.querythrottler.MetricRule.fromObject(object.metric_rules[keys[i]]); + } + } + return message; + }; + + /** + * Creates a plain object from a MetricRuleSet message. Also converts values to other types if specified. + * @function toObject + * @memberof querythrottler.MetricRuleSet + * @static + * @param {querythrottler.MetricRuleSet} message MetricRuleSet + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MetricRuleSet.toObject = function toObject(message, options) { + if (!options) + options = {}; + let object = {}; + if (options.objects || options.defaults) + object.metric_rules = {}; + let keys2; + if (message.metric_rules && (keys2 = Object.keys(message.metric_rules)).length) { + object.metric_rules = {}; + for (let j = 0; j < keys2.length; ++j) + object.metric_rules[keys2[j]] = $root.querythrottler.MetricRule.toObject(message.metric_rules[keys2[j]], options); + } + return object; + }; + + /** + * Converts this MetricRuleSet to JSON. + * @function toJSON + * @memberof querythrottler.MetricRuleSet + * @instance + * @returns {Object.} JSON object + */ + MetricRuleSet.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for MetricRuleSet + * @function getTypeUrl + * @memberof querythrottler.MetricRuleSet + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + MetricRuleSet.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/querythrottler.MetricRuleSet"; + }; + + return MetricRuleSet; + })(); + + querythrottler.MetricRule = (function() { + + /** + * Properties of a MetricRule. + * @memberof querythrottler + * @interface IMetricRule + * @property {Array.|null} [thresholds] MetricRule thresholds + */ + + /** + * Constructs a new MetricRule. + * @memberof querythrottler + * @classdesc Represents a MetricRule. + * @implements IMetricRule + * @constructor + * @param {querythrottler.IMetricRule=} [properties] Properties to set + */ + function MetricRule(properties) { + this.thresholds = []; + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * MetricRule thresholds. + * @member {Array.} thresholds + * @memberof querythrottler.MetricRule + * @instance + */ + MetricRule.prototype.thresholds = $util.emptyArray; + + /** + * Creates a new MetricRule instance using the specified properties. + * @function create + * @memberof querythrottler.MetricRule + * @static + * @param {querythrottler.IMetricRule=} [properties] Properties to set + * @returns {querythrottler.MetricRule} MetricRule instance + */ + MetricRule.create = function create(properties) { + return new MetricRule(properties); + }; + + /** + * Encodes the specified MetricRule message. Does not implicitly {@link querythrottler.MetricRule.verify|verify} messages. + * @function encode + * @memberof querythrottler.MetricRule + * @static + * @param {querythrottler.IMetricRule} message MetricRule message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MetricRule.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.thresholds != null && message.thresholds.length) + for (let i = 0; i < message.thresholds.length; ++i) + $root.querythrottler.ThrottleThreshold.encode(message.thresholds[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified MetricRule message, length delimited. Does not implicitly {@link querythrottler.MetricRule.verify|verify} messages. + * @function encodeDelimited + * @memberof querythrottler.MetricRule + * @static + * @param {querythrottler.IMetricRule} message MetricRule message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MetricRule.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MetricRule message from the specified reader or buffer. + * @function decode + * @memberof querythrottler.MetricRule + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {querythrottler.MetricRule} MetricRule + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MetricRule.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.querythrottler.MetricRule(); + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (!(message.thresholds && message.thresholds.length)) + message.thresholds = []; + message.thresholds.push($root.querythrottler.ThrottleThreshold.decode(reader, reader.uint32())); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MetricRule message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof querythrottler.MetricRule + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {querythrottler.MetricRule} MetricRule + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MetricRule.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MetricRule message. + * @function verify + * @memberof querythrottler.MetricRule + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MetricRule.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.thresholds != null && message.hasOwnProperty("thresholds")) { + if (!Array.isArray(message.thresholds)) + return "thresholds: array expected"; + for (let i = 0; i < message.thresholds.length; ++i) { + let error = $root.querythrottler.ThrottleThreshold.verify(message.thresholds[i]); + if (error) + return "thresholds." + error; + } + } + return null; + }; + + /** + * Creates a MetricRule message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof querythrottler.MetricRule + * @static + * @param {Object.} object Plain object + * @returns {querythrottler.MetricRule} MetricRule + */ + MetricRule.fromObject = function fromObject(object) { + if (object instanceof $root.querythrottler.MetricRule) + return object; + let message = new $root.querythrottler.MetricRule(); + if (object.thresholds) { + if (!Array.isArray(object.thresholds)) + throw TypeError(".querythrottler.MetricRule.thresholds: array expected"); + message.thresholds = []; + for (let i = 0; i < object.thresholds.length; ++i) { + if (typeof object.thresholds[i] !== "object") + throw TypeError(".querythrottler.MetricRule.thresholds: object expected"); + message.thresholds[i] = $root.querythrottler.ThrottleThreshold.fromObject(object.thresholds[i]); + } + } + return message; + }; + + /** + * Creates a plain object from a MetricRule message. Also converts values to other types if specified. + * @function toObject + * @memberof querythrottler.MetricRule + * @static + * @param {querythrottler.MetricRule} message MetricRule + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MetricRule.toObject = function toObject(message, options) { + if (!options) + options = {}; + let object = {}; + if (options.arrays || options.defaults) + object.thresholds = []; + if (message.thresholds && message.thresholds.length) { + object.thresholds = []; + for (let j = 0; j < message.thresholds.length; ++j) + object.thresholds[j] = $root.querythrottler.ThrottleThreshold.toObject(message.thresholds[j], options); + } + return object; + }; + + /** + * Converts this MetricRule to JSON. + * @function toJSON + * @memberof querythrottler.MetricRule + * @instance + * @returns {Object.} JSON object + */ + MetricRule.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for MetricRule + * @function getTypeUrl + * @memberof querythrottler.MetricRule + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + MetricRule.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/querythrottler.MetricRule"; + }; + + return MetricRule; + })(); + + querythrottler.ThrottleThreshold = (function() { + + /** + * Properties of a ThrottleThreshold. + * @memberof querythrottler + * @interface IThrottleThreshold + * @property {number|null} [above] ThrottleThreshold above + * @property {number|null} [throttle] ThrottleThreshold throttle + */ + + /** + * Constructs a new ThrottleThreshold. + * @memberof querythrottler + * @classdesc Represents a ThrottleThreshold. + * @implements IThrottleThreshold + * @constructor + * @param {querythrottler.IThrottleThreshold=} [properties] Properties to set + */ + function ThrottleThreshold(properties) { + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * ThrottleThreshold above. + * @member {number} above + * @memberof querythrottler.ThrottleThreshold + * @instance + */ + ThrottleThreshold.prototype.above = 0; + + /** + * ThrottleThreshold throttle. + * @member {number} throttle + * @memberof querythrottler.ThrottleThreshold + * @instance + */ + ThrottleThreshold.prototype.throttle = 0; + + /** + * Creates a new ThrottleThreshold instance using the specified properties. + * @function create + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {querythrottler.IThrottleThreshold=} [properties] Properties to set + * @returns {querythrottler.ThrottleThreshold} ThrottleThreshold instance + */ + ThrottleThreshold.create = function create(properties) { + return new ThrottleThreshold(properties); + }; + + /** + * Encodes the specified ThrottleThreshold message. Does not implicitly {@link querythrottler.ThrottleThreshold.verify|verify} messages. + * @function encode + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {querythrottler.IThrottleThreshold} message ThrottleThreshold message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ThrottleThreshold.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.above != null && Object.hasOwnProperty.call(message, "above")) + writer.uint32(/* id 1, wireType 1 =*/9).double(message.above); + if (message.throttle != null && Object.hasOwnProperty.call(message, "throttle")) + writer.uint32(/* id 2, wireType 0 =*/16).int32(message.throttle); + return writer; + }; + + /** + * Encodes the specified ThrottleThreshold message, length delimited. Does not implicitly {@link querythrottler.ThrottleThreshold.verify|verify} messages. + * @function encodeDelimited + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {querythrottler.IThrottleThreshold} message ThrottleThreshold message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ThrottleThreshold.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a ThrottleThreshold message from the specified reader or buffer. + * @function decode + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {querythrottler.ThrottleThreshold} ThrottleThreshold + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ThrottleThreshold.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.querythrottler.ThrottleThreshold(); + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.above = reader.double(); + break; + } + case 2: { + message.throttle = reader.int32(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a ThrottleThreshold message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {querythrottler.ThrottleThreshold} ThrottleThreshold + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ThrottleThreshold.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a ThrottleThreshold message. + * @function verify + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + ThrottleThreshold.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.above != null && message.hasOwnProperty("above")) + if (typeof message.above !== "number") + return "above: number expected"; + if (message.throttle != null && message.hasOwnProperty("throttle")) + if (!$util.isInteger(message.throttle)) + return "throttle: integer expected"; + return null; + }; + + /** + * Creates a ThrottleThreshold message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {Object.} object Plain object + * @returns {querythrottler.ThrottleThreshold} ThrottleThreshold + */ + ThrottleThreshold.fromObject = function fromObject(object) { + if (object instanceof $root.querythrottler.ThrottleThreshold) + return object; + let message = new $root.querythrottler.ThrottleThreshold(); + if (object.above != null) + message.above = Number(object.above); + if (object.throttle != null) + message.throttle = object.throttle | 0; + return message; + }; + + /** + * Creates a plain object from a ThrottleThreshold message. Also converts values to other types if specified. + * @function toObject + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {querythrottler.ThrottleThreshold} message ThrottleThreshold + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + ThrottleThreshold.toObject = function toObject(message, options) { + if (!options) + options = {}; + let object = {}; + if (options.defaults) { + object.above = 0; + object.throttle = 0; + } + if (message.above != null && message.hasOwnProperty("above")) + object.above = options.json && !isFinite(message.above) ? String(message.above) : message.above; + if (message.throttle != null && message.hasOwnProperty("throttle")) + object.throttle = message.throttle; + return object; + }; + + /** + * Converts this ThrottleThreshold to JSON. + * @function toJSON + * @memberof querythrottler.ThrottleThreshold + * @instance + * @returns {Object.} JSON object + */ + ThrottleThreshold.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for ThrottleThreshold + * @function getTypeUrl + * @memberof querythrottler.ThrottleThreshold + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + ThrottleThreshold.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/querythrottler.ThrottleThreshold"; + }; + + return ThrottleThreshold; + })(); + + return querythrottler; +})(); + export const vtrpc = $root.vtrpc = (() => { /**