diff --git a/pkg/mcs/resource_manager/client/config.go b/pkg/mcs/resource_manager/client/config.go new file mode 100644 index 00000000000..dd578d56557 --- /dev/null +++ b/pkg/mcs/resource_manager/client/config.go @@ -0,0 +1,83 @@ +// Copyright 2023 TiKV Project 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,g +// 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 client + +const ( + defaultReadBaseCost = 1 + defaultReadCostPerByte = 1. / 1024 / 1024 + defaultReadCPUMsCost = 1 + defaultWriteBaseCost = 3 + defaultWriteCostPerByte = 5. / 1024 / 1024 +) + +// RequestUnitConfig is the configuration of the request units, which determines the coefficients of +// the RRU and WRU cost. This configuration should be modified carefully. +type RequestUnitConfig struct { + // ReadBaseCost is the base cost for a read request. No matter how many bytes read/written or + // the CPU times taken for a request, this cost is inevitable. + ReadBaseCost float64 `toml:"read-base-cost" json:"read-base-cost"` + // ReadCostPerByte is the cost for each byte read. It's 1 MiB = 1 RRU by default. + ReadCostPerByte float64 `toml:"read-cost-per-byte" json:"read-cost-per-byte"` + // ReadCPUMsCost is the cost for each millisecond of CPU time taken by a read request. + // It's 1 millisecond = 1 RRU by default. + ReadCPUMsCost float64 `toml:"read-cpu-ms-cost" json:"read-cpu-ms-cost"` + // WriteBaseCost is the base cost for a write request. No matter how many bytes read/written or + // the CPU times taken for a request, this cost is inevitable. + WriteBaseCost float64 `toml:"write-base-cost" json:"write-base-cost"` + // WriteCostPerByte is the cost for each byte written. It's 1 MiB = 5 WRU by default. + WriteCostPerByte float64 `toml:"write-cost-per-byte" json:"write-cost-per-byte"` +} + +// DefaultRequestUnitConfig returns the default request unit configuration. +func DefaultRequestUnitConfig() *RequestUnitConfig { + return &RequestUnitConfig{ + ReadBaseCost: defaultReadBaseCost, + ReadCostPerByte: defaultReadCostPerByte, + ReadCPUMsCost: defaultReadCPUMsCost, + WriteBaseCost: defaultWriteBaseCost, + WriteCostPerByte: defaultWriteCostPerByte, + } +} + +// Config is the configuration of the resource units, which gives the read/write request +// units or request resource cost standards. It should be calculated by a given `RequestUnitConfig` +// or `RequestResourceConfig`. +type Config struct { + ReadBaseCost RequestUnit + ReadBytesCost RequestUnit + ReadCPUMsCost RequestUnit + WriteBaseCost RequestUnit + WriteBytesCost RequestUnit + // TODO: add SQL computing CPU cost. +} + +// DefaultConfig returns the default configuration. +func DefaultConfig() *Config { + cfg := generateConfig( + DefaultRequestUnitConfig(), + ) + return cfg +} + +func generateConfig(ruConfig *RequestUnitConfig) *Config { + cfg := &Config{ + ReadBaseCost: RequestUnit(ruConfig.ReadBaseCost), + ReadBytesCost: RequestUnit(ruConfig.ReadCostPerByte), + ReadCPUMsCost: RequestUnit(ruConfig.ReadCPUMsCost), + WriteBaseCost: RequestUnit(ruConfig.WriteBaseCost), + WriteBytesCost: RequestUnit(ruConfig.WriteCostPerByte), + } + return cfg +} diff --git a/pkg/mcs/resource_manager/client/model.go b/pkg/mcs/resource_manager/client/model.go new file mode 100644 index 00000000000..64a6303774c --- /dev/null +++ b/pkg/mcs/resource_manager/client/model.go @@ -0,0 +1,122 @@ +// Copyright 2023 TiKV Project 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,g +// 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 client + +import ( + "context" + + rmpb "github.com/pingcap/kvproto/pkg/resource_manager" +) + +// RequestUnit is the basic unit of the resource request management, which has two types: +// - RRU: read request unit +// - WRU: write request unit +type RequestUnit float64 + +// RequestInfo is the interface of the request information provider. A request should be +// able tell whether it's a write request and if so, the written bytes would also be provided. +type RequestInfo interface { + IsWrite() bool + WriteBytes() uint64 +} + +// ResponseInfo is the interface of the response information provider. A response should be +// able tell how many bytes it read and KV CPU cost in milliseconds. +type ResponseInfo interface { + ReadBytes() uint64 + KVCPUMs() uint64 +} + +// ResourceCalculator is used to calculate the resource consumption of a request. +type ResourceCalculator interface { + // Trickle is used to calculate the resource consumption periodically rather than on the request path. + // It's mainly used to calculate like the SQL CPU cost. + Trickle(context.Context, *rmpb.Consumption) + // BeforeKVRequest is used to calculate the resource consumption before the KV request. + // It's mainly used to calculate the base and write request cost. + BeforeKVRequest(*rmpb.Consumption, RequestInfo) + // AfterKVRequest is used to calculate the resource consumption after the KV request. + // It's mainly used to calculate the read request cost and KV CPU cost. + AfterKVRequest(*rmpb.Consumption, RequestInfo, ResponseInfo) +} + +// KVCalculator is used to calculate the KV-side consumption. +type KVCalculator struct { + *Config +} + +var _ ResourceCalculator = (*KVCalculator)(nil) + +// func newKVCalculator(cfg *Config) *KVCalculator { +// return &KVCalculator{Config: cfg} +// } + +// Trickle ... +func (kc *KVCalculator) Trickle(ctx context.Context, consumption *rmpb.Consumption) { +} + +// BeforeKVRequest ... +func (kc *KVCalculator) BeforeKVRequest(consumption *rmpb.Consumption, req RequestInfo) { + if req.IsWrite() { + consumption.KvWriteRpcCount += 1 + // Write bytes are knowable in advance, so we can calculate the WRU cost here. + writeBytes := float64(req.WriteBytes()) + consumption.WriteBytes += writeBytes + consumption.WRU += float64(kc.WriteBaseCost) + float64(kc.WriteBytesCost)*writeBytes + } else { + consumption.KvReadRpcCount += 1 + // Read bytes could not be known before the request is executed, + // so we only add the base cost here. + consumption.RRU += float64(kc.ReadBaseCost) + } +} + +// AfterKVRequest ... +func (kc *KVCalculator) AfterKVRequest(consumption *rmpb.Consumption, req RequestInfo, res ResponseInfo) { + // For now, we can only collect the KV CPU cost for a read request. + if !req.IsWrite() { + kvCPUMs := float64(res.KVCPUMs()) + consumption.TotalCpuTimeMs += kvCPUMs + consumption.RRU += float64(kc.ReadCPUMsCost) * kvCPUMs + } + // A write request may also read data, which should be counted into the RRU cost. + readBytes := float64(res.ReadBytes()) + consumption.ReadBytes += readBytes + consumption.RRU += float64(kc.ReadBytesCost) * readBytes +} + +// SQLCalculator is used to calculate the SQL-side consumption. +type SQLCalculator struct { + *Config +} + +var _ ResourceCalculator = (*SQLCalculator)(nil) + +// func newSQLCalculator(cfg *Config) *SQLCalculator { +// return &SQLCalculator{Config: cfg} +// } + +// Trickle ... +// TODO: calculate the SQL CPU cost and related resource consumption. +func (dsc *SQLCalculator) Trickle(ctx context.Context, consumption *rmpb.Consumption) { +} + +// BeforeKVRequest ... +func (dsc *SQLCalculator) BeforeKVRequest(consumption *rmpb.Consumption, req RequestInfo) { +} + +// AfterKVRequest ... +func (dsc *SQLCalculator) AfterKVRequest(consumption *rmpb.Consumption, req RequestInfo, res ResponseInfo) { +}