From 00b2bdbb3baece74b0c07de62f7c0967852cfc2e Mon Sep 17 00:00:00 2001 From: StLeoX Date: Fri, 28 Jun 2024 16:01:22 +0800 Subject: [PATCH 01/28] [API] MeasureAggregateFunctionService Support --- api/proto/banyandb/database/v1/rpc.proto | 13 +++++ api/proto/banyandb/database/v1/schema.proto | 10 ++++ docs/api-reference.md | 56 +++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/api/proto/banyandb/database/v1/rpc.proto b/api/proto/banyandb/database/v1/rpc.proto index a3429b7df..313ebdd8c 100644 --- a/api/proto/banyandb/database/v1/rpc.proto +++ b/api/proto/banyandb/database/v1/rpc.proto @@ -483,3 +483,16 @@ service TopNAggregationRegistryService { // Exist doesn't expose an HTTP endpoint. Please use HEAD method to touch Get instead rpc Exist(TopNAggregationRegistryServiceExistRequest) returns (TopNAggregationRegistryServiceExistResponse); } + +message MeasureAggregateFunctionServiceSupportRequest{} + +message MeasureAggregateFunctionServiceSupportResponse{ + repeated banyandb.database.v1.MeasureAggregateFunction measure_aggregate_function = 1; +} + +service MeasureAggregateFunctionService { + // Support doesn't need metadata, it's static and stateless. + rpc Support(MeasureAggregateFunctionServiceSupportRequest) returns (MeasureAggregateFunctionServiceSupportResponse) { + option (google.api.http) = {get: "/v1/measure-agg/schema/support"}; + } +} diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto index 7941866c3..ed9a1e6a8 100644 --- a/api/proto/banyandb/database/v1/schema.proto +++ b/api/proto/banyandb/database/v1/schema.proto @@ -21,6 +21,7 @@ package banyandb.database.v1; import "banyandb/common/v1/common.proto"; import "banyandb/model/v1/query.proto"; +import "banyandb/model/v1/common.proto"; import "google/protobuf/timestamp.proto"; import "validate/validate.proto"; @@ -95,6 +96,8 @@ message FieldSpec { EncodingMethod encoding_method = 3 [(validate.rules).enum.defined_only = true]; // compression_method indicates how to compress data during writing CompressionMethod compression_method = 4 [(validate.rules).enum.defined_only = true]; + // aggregate_function indicates how to aggregate data + model.v1.AggregationFunction aggregate_function = 5; } // Measure intends to store data point @@ -114,6 +117,13 @@ message Measure { google.protobuf.Timestamp updated_at = 6; } +message MeasureAggregateFunction { + // type indicates the type of function argument + FieldType type = 1 [(validate.rules).enum.defined_only = true]; + // aggregate_function indicates specific function for measure data + model.v1.AggregationFunction aggregate_function = 2; +} + // TopNAggregation generates offline TopN statistics for a measure's TopN approximation message TopNAggregation { // metadata is the identity of an aggregation diff --git a/docs/api-reference.md b/docs/api-reference.md index c4107a80e..420b01c2a 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -62,6 +62,7 @@ - [IndexRule](#banyandb-database-v1-IndexRule) - [IndexRuleBinding](#banyandb-database-v1-IndexRuleBinding) - [Measure](#banyandb-database-v1-Measure) + - [MeasureAggregateFunction](#banyandb-database-v1-MeasureAggregateFunction) - [Stream](#banyandb-database-v1-Stream) - [Subject](#banyandb-database-v1-Subject) - [TagFamilySpec](#banyandb-database-v1-TagFamilySpec) @@ -112,6 +113,8 @@ - [IndexRuleRegistryServiceListResponse](#banyandb-database-v1-IndexRuleRegistryServiceListResponse) - [IndexRuleRegistryServiceUpdateRequest](#banyandb-database-v1-IndexRuleRegistryServiceUpdateRequest) - [IndexRuleRegistryServiceUpdateResponse](#banyandb-database-v1-IndexRuleRegistryServiceUpdateResponse) + - [MeasureAggregateFunctionServiceSupportRequest](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportRequest) + - [MeasureAggregateFunctionServiceSupportResponse](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportResponse) - [MeasureRegistryServiceCreateRequest](#banyandb-database-v1-MeasureRegistryServiceCreateRequest) - [MeasureRegistryServiceCreateResponse](#banyandb-database-v1-MeasureRegistryServiceCreateResponse) - [MeasureRegistryServiceDeleteRequest](#banyandb-database-v1-MeasureRegistryServiceDeleteRequest) @@ -152,6 +155,7 @@ - [GroupRegistryService](#banyandb-database-v1-GroupRegistryService) - [IndexRuleBindingRegistryService](#banyandb-database-v1-IndexRuleBindingRegistryService) - [IndexRuleRegistryService](#banyandb-database-v1-IndexRuleRegistryService) + - [MeasureAggregateFunctionService](#banyandb-database-v1-MeasureAggregateFunctionService) - [MeasureRegistryService](#banyandb-database-v1-MeasureRegistryService) - [StreamRegistryService](#banyandb-database-v1-StreamRegistryService) - [TopNAggregationRegistryService](#banyandb-database-v1-TopNAggregationRegistryService) @@ -952,6 +956,7 @@ FieldSpec is the specification of field | field_type | [FieldType](#banyandb-database-v1-FieldType) | | field_type denotes the type of field value | | encoding_method | [EncodingMethod](#banyandb-database-v1-EncodingMethod) | | encoding_method indicates how to encode data during writing | | compression_method | [CompressionMethod](#banyandb-database-v1-CompressionMethod) | | compression_method indicates how to compress data during writing | +| aggregate_function | [banyandb.model.v1.AggregationFunction](#banyandb-model-v1-AggregationFunction) | | aggregate_function indicates how to aggregate data | @@ -1021,6 +1026,22 @@ Measure intends to store data point + + +### MeasureAggregateFunction + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [FieldType](#banyandb-database-v1-FieldType) | | type indicates the type of function argument | +| aggregate_function | [banyandb.model.v1.AggregationFunction](#banyandb-model-v1-AggregationFunction) | | aggregate_function indicates specific function for measure data | + + + + + + ### Stream @@ -1715,6 +1736,31 @@ Type determine the index structure under the hood + + +### MeasureAggregateFunctionServiceSupportRequest + + + + + + + + + +### MeasureAggregateFunctionServiceSupportResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| measure_aggregate_function | [MeasureAggregateFunction](#banyandb-database-v1-MeasureAggregateFunction) | repeated | | + + + + + + ### MeasureRegistryServiceCreateRequest @@ -2299,6 +2345,16 @@ Type determine the index structure under the hood | Exist | [IndexRuleRegistryServiceExistRequest](#banyandb-database-v1-IndexRuleRegistryServiceExistRequest) | [IndexRuleRegistryServiceExistResponse](#banyandb-database-v1-IndexRuleRegistryServiceExistResponse) | Exist doesn't expose an HTTP endpoint. Please use HEAD method to touch Get instead | + + +### MeasureAggregateFunctionService + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| Support | [MeasureAggregateFunctionServiceSupportRequest](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportRequest) | [MeasureAggregateFunctionServiceSupportResponse](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportResponse) | Support doesn't need metadata, it's static and stateless. | + + ### MeasureRegistryService From 38aec7ffa79e39252733c200870f7e4a0b7a18eb Mon Sep 17 00:00:00 2001 From: StLeoX Date: Sat, 29 Jun 2024 11:08:09 +0800 Subject: [PATCH 02/28] fix check --- api/proto/banyandb/database/v1/rpc.proto | 4 ++-- api/proto/banyandb/database/v1/schema.proto | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/proto/banyandb/database/v1/rpc.proto b/api/proto/banyandb/database/v1/rpc.proto index 313ebdd8c..44a48f574 100644 --- a/api/proto/banyandb/database/v1/rpc.proto +++ b/api/proto/banyandb/database/v1/rpc.proto @@ -484,9 +484,9 @@ service TopNAggregationRegistryService { rpc Exist(TopNAggregationRegistryServiceExistRequest) returns (TopNAggregationRegistryServiceExistResponse); } -message MeasureAggregateFunctionServiceSupportRequest{} +message MeasureAggregateFunctionServiceSupportRequest {} -message MeasureAggregateFunctionServiceSupportResponse{ +message MeasureAggregateFunctionServiceSupportResponse { repeated banyandb.database.v1.MeasureAggregateFunction measure_aggregate_function = 1; } diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto index ed9a1e6a8..0bc57e254 100644 --- a/api/proto/banyandb/database/v1/schema.proto +++ b/api/proto/banyandb/database/v1/schema.proto @@ -20,8 +20,8 @@ syntax = "proto3"; package banyandb.database.v1; import "banyandb/common/v1/common.proto"; -import "banyandb/model/v1/query.proto"; import "banyandb/model/v1/common.proto"; +import "banyandb/model/v1/query.proto"; import "google/protobuf/timestamp.proto"; import "validate/validate.proto"; From ad3b14ac093d4339e7cff11da97bade9d278c14e Mon Sep 17 00:00:00 2001 From: StLeoX Date: Wed, 31 Jul 2024 22:38:57 +0800 Subject: [PATCH 03/28] use MeasureAggregate enum --- api/proto/banyandb/database/v1/schema.proto | 2 +- api/proto/banyandb/model/v1/common.proto | 14 ++++++++++++ docs/api-reference.md | 24 ++++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto index 0bc57e254..e96a8ac43 100644 --- a/api/proto/banyandb/database/v1/schema.proto +++ b/api/proto/banyandb/database/v1/schema.proto @@ -121,7 +121,7 @@ message MeasureAggregateFunction { // type indicates the type of function argument FieldType type = 1 [(validate.rules).enum.defined_only = true]; // aggregate_function indicates specific function for measure data - model.v1.AggregationFunction aggregate_function = 2; + model.v1.MeasureAggregate aggregate_function = 2; } // TopNAggregation generates offline TopN statistics for a measure's TopN approximation diff --git a/api/proto/banyandb/model/v1/common.proto b/api/proto/banyandb/model/v1/common.proto index f639c39bb..586f6c5f3 100644 --- a/api/proto/banyandb/model/v1/common.proto +++ b/api/proto/banyandb/model/v1/common.proto @@ -77,3 +77,17 @@ enum AggregationFunction { AGGREGATION_FUNCTION_COUNT = 4; AGGREGATION_FUNCTION_SUM = 5; } + +enum MeasureAggregate { + MEASURE_AGGREGATE_UNSPECIFIED = 0; + MEASURE_AGGREGATE_MIN = 1; + MEASURE_AGGREGATE_MAX = 2; + MEASURE_AGGREGATE_COUNT = 3; + MEASURE_AGGREGATE_SUM = 4; + MEASURE_AGGREGATE_AVG = 5; + MEASURE_AGGREGATE_PERCENT = 6; + MEASURE_AGGREGATE_RATE = 7; + MEASURE_AGGREGATE_HISTOGRAM = 8; + MEASURE_AGGREGATE_PERCENTILE2 = 9; + MEASURE_AGGREGATE_APDEX = 10; +} diff --git a/docs/api-reference.md b/docs/api-reference.md index 420b01c2a..e587c5963 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -40,6 +40,7 @@ - [TagValue](#banyandb-model-v1-TagValue) - [AggregationFunction](#banyandb-model-v1-AggregationFunction) + - [MeasureAggregate](#banyandb-model-v1-MeasureAggregate) - [banyandb/model/v1/query.proto](#banyandb_model_v1_query-proto) - [Condition](#banyandb-model-v1-Condition) @@ -692,6 +693,27 @@ Trace is the top level message of a trace. | AGGREGATION_FUNCTION_SUM | 5 | | + + + +### MeasureAggregate + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| MEASURE_AGGREGATE_UNSPECIFIED | 0 | | +| MEASURE_AGGREGATE_MIN | 1 | | +| MEASURE_AGGREGATE_MAX | 2 | | +| MEASURE_AGGREGATE_COUNT | 3 | | +| MEASURE_AGGREGATE_SUM | 4 | | +| MEASURE_AGGREGATE_AVG | 5 | | +| MEASURE_AGGREGATE_PERCENT | 6 | | +| MEASURE_AGGREGATE_RATE | 7 | | +| MEASURE_AGGREGATE_HISTOGRAM | 8 | | +| MEASURE_AGGREGATE_PERCENTILE2 | 9 | | +| MEASURE_AGGREGATE_APDEX | 10 | | + + @@ -1035,7 +1057,7 @@ Measure intends to store data point | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | type | [FieldType](#banyandb-database-v1-FieldType) | | type indicates the type of function argument | -| aggregate_function | [banyandb.model.v1.AggregationFunction](#banyandb-model-v1-AggregationFunction) | | aggregate_function indicates specific function for measure data | +| aggregate_function | [banyandb.model.v1.MeasureAggregate](#banyandb-model-v1-MeasureAggregate) | | aggregate_function indicates specific function for measure data | From e05a97b3fea929ebb45fe2bd5a54d3ac851bb2b8 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 15 Aug 2024 14:33:35 +0800 Subject: [PATCH 04/28] add PERCENTILE enum for MEASURE_AGGREGATE --- api/proto/banyandb/model/v1/common.proto | 1 + docs/api-reference.md | 1 + 2 files changed, 2 insertions(+) diff --git a/api/proto/banyandb/model/v1/common.proto b/api/proto/banyandb/model/v1/common.proto index 586f6c5f3..6b805a96e 100644 --- a/api/proto/banyandb/model/v1/common.proto +++ b/api/proto/banyandb/model/v1/common.proto @@ -90,4 +90,5 @@ enum MeasureAggregate { MEASURE_AGGREGATE_HISTOGRAM = 8; MEASURE_AGGREGATE_PERCENTILE2 = 9; MEASURE_AGGREGATE_APDEX = 10; + MEASURE_AGGREGATE_PERCENTILE = 11; } diff --git a/docs/api-reference.md b/docs/api-reference.md index e452e56d5..3b6fc8cec 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -714,6 +714,7 @@ Trace is the top level message of a trace. | MEASURE_AGGREGATE_HISTOGRAM | 8 | | | MEASURE_AGGREGATE_PERCENTILE2 | 9 | | | MEASURE_AGGREGATE_APDEX | 10 | | +| MEASURE_AGGREGATE_PERCENTILE | 11 | | From e1b7e9889d6f566e19cf2aa3e5e9a0e773dd10bc Mon Sep 17 00:00:00 2001 From: StLeoX Date: Fri, 16 Aug 2024 11:23:44 +0800 Subject: [PATCH 05/28] add comments for MeasureAggregate enum --- api/proto/banyandb/model/v1/common.proto | 11 +++++++++++ docs/api-reference.md | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/proto/banyandb/model/v1/common.proto b/api/proto/banyandb/model/v1/common.proto index 6b805a96e..b54b7a050 100644 --- a/api/proto/banyandb/model/v1/common.proto +++ b/api/proto/banyandb/model/v1/common.proto @@ -80,15 +80,26 @@ enum AggregationFunction { enum MeasureAggregate { MEASURE_AGGREGATE_UNSPECIFIED = 0; + // Calculate the minimum value of delta measures. MEASURE_AGGREGATE_MIN = 1; + // Calculate the maximum value of delta measures. MEASURE_AGGREGATE_MAX = 2; + // Count the number of delta measures. MEASURE_AGGREGATE_COUNT = 3; + // Calculate the sum value of delta measures. MEASURE_AGGREGATE_SUM = 4; + // Calculate the average value of delta measures. MEASURE_AGGREGATE_AVG = 5; + // Calculate the percentage of delta measures, where the input matches with the condition. MEASURE_AGGREGATE_PERCENT = 6; + // Calculate the ratio for measures, where the input matches with the condition. MEASURE_AGGREGATE_RATE = 7; + // Calculate the histogram for delta measures. MEASURE_AGGREGATE_HISTOGRAM = 8; + // Calculate the {p99, p95, p90, p75, p50} for delta measures. MEASURE_AGGREGATE_PERCENTILE2 = 9; + // Calculate the apdex for delta measures. MEASURE_AGGREGATE_APDEX = 10; + // Same like PERCENTILE2, little different on algorithm. MEASURE_AGGREGATE_PERCENTILE = 11; } diff --git a/docs/api-reference.md b/docs/api-reference.md index 3b6fc8cec..d84f9b4f5 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -704,17 +704,17 @@ Trace is the top level message of a trace. | Name | Number | Description | | ---- | ------ | ----------- | | MEASURE_AGGREGATE_UNSPECIFIED | 0 | | -| MEASURE_AGGREGATE_MIN | 1 | | -| MEASURE_AGGREGATE_MAX | 2 | | -| MEASURE_AGGREGATE_COUNT | 3 | | -| MEASURE_AGGREGATE_SUM | 4 | | -| MEASURE_AGGREGATE_AVG | 5 | | -| MEASURE_AGGREGATE_PERCENT | 6 | | -| MEASURE_AGGREGATE_RATE | 7 | | -| MEASURE_AGGREGATE_HISTOGRAM | 8 | | -| MEASURE_AGGREGATE_PERCENTILE2 | 9 | | -| MEASURE_AGGREGATE_APDEX | 10 | | -| MEASURE_AGGREGATE_PERCENTILE | 11 | | +| MEASURE_AGGREGATE_MIN | 1 | Calculate the minimum value of delta measures. | +| MEASURE_AGGREGATE_MAX | 2 | Calculate the maximum value of delta measures. | +| MEASURE_AGGREGATE_COUNT | 3 | Count the number of delta measures. | +| MEASURE_AGGREGATE_SUM | 4 | Calculate the sum value of delta measures. | +| MEASURE_AGGREGATE_AVG | 5 | Calculate the average value of delta measures. | +| MEASURE_AGGREGATE_PERCENT | 6 | Calculate the percentage of delta measures, where the input matches with the condition. | +| MEASURE_AGGREGATE_RATE | 7 | Calculate the ratio for measures, where the input matches with the condition. | +| MEASURE_AGGREGATE_HISTOGRAM | 8 | Calculate the histogram for delta measures. | +| MEASURE_AGGREGATE_PERCENTILE2 | 9 | Calculate the {p99, p95, p90, p75, p50} for delta measures. | +| MEASURE_AGGREGATE_APDEX | 10 | Calculate the apdex for delta measures. | +| MEASURE_AGGREGATE_PERCENTILE | 11 | Same like PERCENTILE2, little different on algorithm. | From 7c9d27f7f4523e916e47ebef2131a5e4ecf40e14 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 22 Aug 2024 16:41:45 +0800 Subject: [PATCH 06/28] fix enum name error in FieldSpec --- api/proto/banyandb/database/v1/schema.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto index e96a8ac43..b390acf53 100644 --- a/api/proto/banyandb/database/v1/schema.proto +++ b/api/proto/banyandb/database/v1/schema.proto @@ -97,7 +97,7 @@ message FieldSpec { // compression_method indicates how to compress data during writing CompressionMethod compression_method = 4 [(validate.rules).enum.defined_only = true]; // aggregate_function indicates how to aggregate data - model.v1.AggregationFunction aggregate_function = 5; + model.v1.MeasureAggregate aggregate_function = 5; } // Measure intends to store data point From 11ccee19793673d9f54cfad961f135c9d45ec198 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 22 Aug 2024 18:06:46 +0800 Subject: [PATCH 07/28] a --- .../aggregate_function/aggregate_function.go | 68 +++++++++++++++++++ banyand/measure/aggregate_function/min.go | 23 +++++++ .../measure/aggregate_function/min_test.go | 20 ++++++ banyand/measure/aggregate_function/percent.go | 30 ++++++++ .../aggregate_function/percent_test.go | 1 + 5 files changed, 142 insertions(+) create mode 100644 banyand/measure/aggregate_function/aggregate_function.go create mode 100644 banyand/measure/aggregate_function/min.go create mode 100644 banyand/measure/aggregate_function/min_test.go create mode 100644 banyand/measure/aggregate_function/percent.go create mode 100644 banyand/measure/aggregate_function/percent_test.go diff --git a/banyand/measure/aggregate_function/aggregate_function.go b/banyand/measure/aggregate_function/aggregate_function.go new file mode 100644 index 000000000..3225ac42b --- /dev/null +++ b/banyand/measure/aggregate_function/aggregate_function.go @@ -0,0 +1,68 @@ +package aggregate_function + +import ( + "fmt" + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "math" +) + +type MAFInput interface { + ~int64 | ~float64 | ~string +} + +type MAFInputNumber interface { + ~int64 | ~float64 +} + +type MAF[T MAFInput] interface { + // todo [][]T 可能抽象出相关结构“参数数组”。 + Combine(arguments [][]T) error + Result() T +} + +func NewMeasureAggregateFunction[T MAFInput](aggregate modelv1.MeasureAggregate) (MAF[T], error) { + var function MAF[T] + switch aggregate { + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: + function = &Min[T]{value: maxValue[T]()} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT: + function = &Percent[T]{match: zeroValue[T](), total: zeroValue[T]()} + default: + return nil, fmt.Errorf("MeasureAggregate unknown") + } + + return function, nil +} + +func zeroValue[T MAFInput]() T { + var z T + return z +} + +func minValue[N MAFInput]() (r N) { + switch x := any(&r).(type) { + case *int64: + *x = math.MinInt64 + case *float64: + *x = -math.MaxFloat64 + case *string: + *x = "" + default: + panic("unreachable") + } + return +} + +func maxValue[N MAFInput]() (r N) { + switch x := any(&r).(type) { + case *int64: + *x = math.MaxInt64 + case *float64: + *x = math.MaxFloat64 + case *string: + *x = "" + default: + panic("unreachable") + } + return +} diff --git a/banyand/measure/aggregate_function/min.go b/banyand/measure/aggregate_function/min.go new file mode 100644 index 000000000..69797f865 --- /dev/null +++ b/banyand/measure/aggregate_function/min.go @@ -0,0 +1,23 @@ +package aggregate_function + +import "fmt" + +type Min[T MAFInput] struct { + value T +} + +func (m *Min[T]) Combine(arguments [][]T) error { + if len(arguments) != 1 { + return fmt.Errorf("needs one argument") + } + for _, arg := range arguments[0] { + if m.value > arg { + m.value = arg + } + } + return nil +} + +func (m *Min[T]) Result() T { + return m.value +} diff --git a/banyand/measure/aggregate_function/min_test.go b/banyand/measure/aggregate_function/min_test.go new file mode 100644 index 000000000..0df2aad00 --- /dev/null +++ b/banyand/measure/aggregate_function/min_test.go @@ -0,0 +1,20 @@ +package aggregate_function + +import ( + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMin(t *testing.T) { + minInt64, _ := NewMeasureAggregateFunction[int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err := minInt64.Combine([][]int64{{10, 20, 30}}) + assert.NoError(t, err) + assert.Equal(t, int64(10), minInt64.Result()) + + minFloat64, _ := NewMeasureAggregateFunction[float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minFloat64.Combine([][]float64{{1.0, 2.0, 3.0}}) + assert.NoError(t, err) + assert.Equal(t, 1.0, minFloat64.Result()) + +} diff --git a/banyand/measure/aggregate_function/percent.go b/banyand/measure/aggregate_function/percent.go new file mode 100644 index 000000000..8960a3ecc --- /dev/null +++ b/banyand/measure/aggregate_function/percent.go @@ -0,0 +1,30 @@ +package aggregate_function + +import "fmt" + +type Percent[T MAFInput] struct { + match T // todo 这里本来应该写 T。这种混写 T 与 int64 的方式不够优雅?但在文档里确实只接收 int64。 + total T +} + +func (m *Percent[T]) Combine(arguments [][]T) error { + if len(arguments) != 2 { + return fmt.Errorf("needs two argument") + } + for _, arg := range arguments[0] { + m.match += arg + } + for _, arg := range arguments[1] { + m.total += arg + } + return nil +} + +func (m *Percent[T]) Result() T { + switch any(m.match).(type) { + /* case *int64: + return T(m.match*100) / T(m.total) + */ + } + return zeroValue[T]() +} diff --git a/banyand/measure/aggregate_function/percent_test.go b/banyand/measure/aggregate_function/percent_test.go new file mode 100644 index 000000000..2c34ec276 --- /dev/null +++ b/banyand/measure/aggregate_function/percent_test.go @@ -0,0 +1 @@ +package aggregate_function From cfc38117c3da46ccf988b79d137453ccc80f5a03 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 22 Aug 2024 16:41:45 +0800 Subject: [PATCH 08/28] fix wrong enum name in FieldSpec --- api/proto/banyandb/database/v1/schema.proto | 2 +- docs/api-reference.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto index e96a8ac43..b390acf53 100644 --- a/api/proto/banyandb/database/v1/schema.proto +++ b/api/proto/banyandb/database/v1/schema.proto @@ -97,7 +97,7 @@ message FieldSpec { // compression_method indicates how to compress data during writing CompressionMethod compression_method = 4 [(validate.rules).enum.defined_only = true]; // aggregate_function indicates how to aggregate data - model.v1.AggregationFunction aggregate_function = 5; + model.v1.MeasureAggregate aggregate_function = 5; } // Measure intends to store data point diff --git a/docs/api-reference.md b/docs/api-reference.md index d84f9b4f5..f50a82f3f 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -981,7 +981,7 @@ FieldSpec is the specification of field | field_type | [FieldType](#banyandb-database-v1-FieldType) | | field_type denotes the type of field value | | encoding_method | [EncodingMethod](#banyandb-database-v1-EncodingMethod) | | encoding_method indicates how to encode data during writing | | compression_method | [CompressionMethod](#banyandb-database-v1-CompressionMethod) | | compression_method indicates how to compress data during writing | -| aggregate_function | [banyandb.model.v1.AggregationFunction](#banyandb-model-v1-AggregationFunction) | | aggregate_function indicates how to aggregate data | +| aggregate_function | [banyandb.model.v1.MeasureAggregate](#banyandb-model-v1-MeasureAggregate) | | aggregate_function indicates how to aggregate data | From 31763db2ea993247c74cc37f501c0ac5740ea4d2 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Sun, 25 Aug 2024 21:26:52 +0800 Subject: [PATCH 09/28] Supports measure aggregate function avg and min, and test cases. --- .../measure/aggregate/aggregate_function.go | 97 +++++++++++++++++++ banyand/measure/aggregate/avg.go | 65 +++++++++++++ banyand/measure/aggregate/avg_test.go | 39 ++++++++ banyand/measure/aggregate/min.go | 54 +++++++++++ banyand/measure/aggregate/min_test.go | 70 +++++++++++++ 5 files changed, 325 insertions(+) create mode 100644 banyand/measure/aggregate/aggregate_function.go create mode 100644 banyand/measure/aggregate/avg.go create mode 100644 banyand/measure/aggregate/avg_test.go create mode 100644 banyand/measure/aggregate/min.go create mode 100644 banyand/measure/aggregate/min_test.go diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go new file mode 100644 index 000000000..3d61764c4 --- /dev/null +++ b/banyand/measure/aggregate/aggregate_function.go @@ -0,0 +1,97 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate for measure aggregate function. +package aggregate + +import ( + "fmt" + "math" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" +) + +// Void type contains nothing. It works as a placeholder for type parameter. +type Void struct{} + +// MAFInput synchronizes with `pbv1.ValueType`, excluding `ValueTypeUnknown` +// and `ValueTypeBinaryData`. +type MAFInput interface { + ~string | ~int64 | ~float64 | ~[]string | ~[]int64 | Void +} + +// MAFKeep represents the only two types of value hold by MAF. +type MAFKeep interface { + ~int64 | ~float64 +} + +var errFieldValueType = fmt.Errorf("unsupported input value type on this field") + +// MAFArgument represents a segment of elements as an argument. +type MAFArgument[T MAFInput] struct { + elements []T +} + +// MAFArguments represents the argument array, with at most three arguments. +type MAFArguments[A, B, C MAFInput] struct { + arg0 MAFArgument[A] + arg1 MAFArgument[B] + arg2 MAFArgument[C] +} + +// MAF describes two stages of aggregation. +type MAF[A, B, C MAFInput, K MAFKeep] interface { + // Combine takes elements to do the aggregation. + // It uses a two-dimensional array to represent the argument array. + Combine(arguments MAFArguments[A, B, C]) error + + // Result gives the result for the aggregation. + // It uses "keep" value type to represent output value type. + Result() K +} + +// NewMeasureAggregateFunction is the factory for MAF. +func NewMeasureAggregateFunction[A, B, C MAFInput, K MAFKeep](aggregate modelv1.MeasureAggregate) (MAF[A, B, C, K], error) { + var function MAF[A, B, C, K] + switch aggregate { + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: + function = &Min[A, B, C, K]{minimum: maxValue[K]()} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: + function = &Avg[A, B, C, K]{summation: zeroValue[K](), count: 0} + default: + return nil, fmt.Errorf("MeasureAggregate unknown") + } + + return function, nil +} + +func zeroValue[K MAFKeep]() K { + var z K + return z +} + +func maxValue[K MAFKeep]() (r K) { + switch x := any(&r).(type) { + case *int64: + *x = math.MaxInt64 + case *float64: + *x = math.MaxFloat64 + default: + panic("unreachable") + } + return +} diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go new file mode 100644 index 000000000..247ac46ea --- /dev/null +++ b/banyand/measure/aggregate/avg.go @@ -0,0 +1,65 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Avg calculates the average value of elements. +type Avg[A, B, C MAFInput, K MAFKeep] struct { + summation K + count int64 +} + +// Combine takes elements to do the aggregation. +// Avg uses type parameter A and B. +func (a *Avg[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { + for _, arg0 := range arguments.arg0.elements { + switch arg0 := any(arg0).(type) { + case int64: + a.summation += K(arg0) + case float64: + a.summation += K(arg0) + case []int64: + for _, v := range arg0 { + a.summation += K(v) + } + default: + return errFieldValueType + } + } + + for _, arg1 := range arguments.arg1.elements { + switch arg1 := any(arg1).(type) { + case int64: + a.count += arg1 + default: + return errFieldValueType + } + } + + return nil +} + +// Result gives the result for the aggregation. +func (a *Avg[A, B, C, K]) Result() K { + // In unusual situations it returns the zero value. + if a.count == 0 { + return zeroValue[K]() + } + // According to the semantics of GoLang, the division of one int by another int + // returns an int, instead of a float. + return a.summation / K(a.count) +} diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go new file mode 100644 index 000000000..50a3d54ad --- /dev/null +++ b/banyand/measure/aggregate/avg_test.go @@ -0,0 +1,39 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" +) + +func TestAvg(t *testing.T) { + var err error + + avgInt64, _ := NewMeasureAggregateFunction[int64, int64, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) + err = avgInt64.Combine(MAFArguments[int64, int64, Void]{ + arg0: MAFArgument[int64]{[]int64{1, 3, 3}}, // mock the "summation" column + arg1: MAFArgument[int64]{[]int64{1, 1, 1}}, // mock the "count" column + arg2: MAFArgument[Void]{}, + }) + assert.NoError(t, err) + assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int +} diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go new file mode 100644 index 000000000..817e23fac --- /dev/null +++ b/banyand/measure/aggregate/min.go @@ -0,0 +1,54 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Min calculates the minimum value of elements. +type Min[A, B, C MAFInput, K MAFKeep] struct { + minimum K +} + +// Combine takes elements to do the aggregation. +// Min uses type parameter A. +func (m *Min[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { + for _, arg0 := range arguments.arg0.elements { + switch arg0 := any(arg0).(type) { + case int64: + if K(arg0) < m.minimum { + m.minimum = K(arg0) + } + case float64: + if K(arg0) < m.minimum { + m.minimum = K(arg0) + } + case []int64: + for _, v := range arg0 { + if K(v) < m.minimum { + m.minimum = K(v) + } + } + default: + return errFieldValueType + } + } + return nil +} + +// Result gives the result for the aggregation. +func (m *Min[A, B, C, K]) Result() K { + return m.minimum +} diff --git a/banyand/measure/aggregate/min_test.go b/banyand/measure/aggregate/min_test.go new file mode 100644 index 000000000..83b9b3cd4 --- /dev/null +++ b/banyand/measure/aggregate/min_test.go @@ -0,0 +1,70 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" +) + +func TestMin(t *testing.T) { + var err error + + // case1: input int64 elements + minInt64, _ := NewMeasureAggregateFunction[int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minInt64.Combine(MAFArguments[int64, Void, Void]{ + arg0: MAFArgument[int64]{[]int64{1, 2, 3}}, + arg1: MAFArgument[Void]{}, + arg2: MAFArgument[Void]{}, + }) + assert.NoError(t, err) + assert.Equal(t, int64(1), minInt64.Result()) + + // case2: input float64 elements + minFloat64, _ := NewMeasureAggregateFunction[float64, Void, Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minFloat64.Combine(MAFArguments[float64, Void, Void]{ + arg0: MAFArgument[float64]{[]float64{1.0, 2.0, 3.0}}, + arg1: MAFArgument[Void]{}, + arg2: MAFArgument[Void]{}, + }) + assert.NoError(t, err) + assert.Equal(t, 1.0, minFloat64.Result()) + + // case3: input []int64 elements + minInt64Arr, _ := NewMeasureAggregateFunction[[]int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minInt64Arr.Combine(MAFArguments[[]int64, Void, Void]{ + arg0: MAFArgument[[]int64]{[][]int64{{1, 2}, {10, 20}}}, + arg1: MAFArgument[Void]{}, + arg2: MAFArgument[Void]{}, + }) + assert.NoError(t, err) + assert.Equal(t, int64(1), minInt64Arr.Result()) + + // case4: unexpected input type + minStr, _ := NewMeasureAggregateFunction[string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minStr.Combine(MAFArguments[string, Void, Void]{ + // fixme If there is no element, can't recognize the wrong input type. It needs at least one variable. + arg0: MAFArgument[string]{[]string{"a"}}, + arg1: MAFArgument[Void]{}, + arg2: MAFArgument[Void]{}, + }) + assert.Errorf(t, err, errFieldValueType.Error()) +} From 98076ffe175171241843e6fd1899f4ba3c0976be Mon Sep 17 00:00:00 2001 From: StLeoX Date: Sun, 25 Aug 2024 21:30:45 +0800 Subject: [PATCH 10/28] Revert "a" This reverts commit 11ccee19793673d9f54cfad961f135c9d45ec198. --- .../aggregate_function/aggregate_function.go | 68 ------------------- banyand/measure/aggregate_function/min.go | 23 ------- .../measure/aggregate_function/min_test.go | 20 ------ banyand/measure/aggregate_function/percent.go | 30 -------- .../aggregate_function/percent_test.go | 1 - 5 files changed, 142 deletions(-) delete mode 100644 banyand/measure/aggregate_function/aggregate_function.go delete mode 100644 banyand/measure/aggregate_function/min.go delete mode 100644 banyand/measure/aggregate_function/min_test.go delete mode 100644 banyand/measure/aggregate_function/percent.go delete mode 100644 banyand/measure/aggregate_function/percent_test.go diff --git a/banyand/measure/aggregate_function/aggregate_function.go b/banyand/measure/aggregate_function/aggregate_function.go deleted file mode 100644 index 3225ac42b..000000000 --- a/banyand/measure/aggregate_function/aggregate_function.go +++ /dev/null @@ -1,68 +0,0 @@ -package aggregate_function - -import ( - "fmt" - modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" - "math" -) - -type MAFInput interface { - ~int64 | ~float64 | ~string -} - -type MAFInputNumber interface { - ~int64 | ~float64 -} - -type MAF[T MAFInput] interface { - // todo [][]T 可能抽象出相关结构“参数数组”。 - Combine(arguments [][]T) error - Result() T -} - -func NewMeasureAggregateFunction[T MAFInput](aggregate modelv1.MeasureAggregate) (MAF[T], error) { - var function MAF[T] - switch aggregate { - case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: - function = &Min[T]{value: maxValue[T]()} - case modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT: - function = &Percent[T]{match: zeroValue[T](), total: zeroValue[T]()} - default: - return nil, fmt.Errorf("MeasureAggregate unknown") - } - - return function, nil -} - -func zeroValue[T MAFInput]() T { - var z T - return z -} - -func minValue[N MAFInput]() (r N) { - switch x := any(&r).(type) { - case *int64: - *x = math.MinInt64 - case *float64: - *x = -math.MaxFloat64 - case *string: - *x = "" - default: - panic("unreachable") - } - return -} - -func maxValue[N MAFInput]() (r N) { - switch x := any(&r).(type) { - case *int64: - *x = math.MaxInt64 - case *float64: - *x = math.MaxFloat64 - case *string: - *x = "" - default: - panic("unreachable") - } - return -} diff --git a/banyand/measure/aggregate_function/min.go b/banyand/measure/aggregate_function/min.go deleted file mode 100644 index 69797f865..000000000 --- a/banyand/measure/aggregate_function/min.go +++ /dev/null @@ -1,23 +0,0 @@ -package aggregate_function - -import "fmt" - -type Min[T MAFInput] struct { - value T -} - -func (m *Min[T]) Combine(arguments [][]T) error { - if len(arguments) != 1 { - return fmt.Errorf("needs one argument") - } - for _, arg := range arguments[0] { - if m.value > arg { - m.value = arg - } - } - return nil -} - -func (m *Min[T]) Result() T { - return m.value -} diff --git a/banyand/measure/aggregate_function/min_test.go b/banyand/measure/aggregate_function/min_test.go deleted file mode 100644 index 0df2aad00..000000000 --- a/banyand/measure/aggregate_function/min_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package aggregate_function - -import ( - modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestMin(t *testing.T) { - minInt64, _ := NewMeasureAggregateFunction[int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err := minInt64.Combine([][]int64{{10, 20, 30}}) - assert.NoError(t, err) - assert.Equal(t, int64(10), minInt64.Result()) - - minFloat64, _ := NewMeasureAggregateFunction[float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minFloat64.Combine([][]float64{{1.0, 2.0, 3.0}}) - assert.NoError(t, err) - assert.Equal(t, 1.0, minFloat64.Result()) - -} diff --git a/banyand/measure/aggregate_function/percent.go b/banyand/measure/aggregate_function/percent.go deleted file mode 100644 index 8960a3ecc..000000000 --- a/banyand/measure/aggregate_function/percent.go +++ /dev/null @@ -1,30 +0,0 @@ -package aggregate_function - -import "fmt" - -type Percent[T MAFInput] struct { - match T // todo 这里本来应该写 T。这种混写 T 与 int64 的方式不够优雅?但在文档里确实只接收 int64。 - total T -} - -func (m *Percent[T]) Combine(arguments [][]T) error { - if len(arguments) != 2 { - return fmt.Errorf("needs two argument") - } - for _, arg := range arguments[0] { - m.match += arg - } - for _, arg := range arguments[1] { - m.total += arg - } - return nil -} - -func (m *Percent[T]) Result() T { - switch any(m.match).(type) { - /* case *int64: - return T(m.match*100) / T(m.total) - */ - } - return zeroValue[T]() -} diff --git a/banyand/measure/aggregate_function/percent_test.go b/banyand/measure/aggregate_function/percent_test.go deleted file mode 100644 index 2c34ec276..000000000 --- a/banyand/measure/aggregate_function/percent_test.go +++ /dev/null @@ -1 +0,0 @@ -package aggregate_function From deefc6ef56e143cb80334e848ea8db595865f218 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Sun, 25 Aug 2024 21:51:46 +0800 Subject: [PATCH 11/28] fix Merge error --- api/proto/banyandb/database/v1/schema.proto | 9 +++ docs/api-reference.md | 78 +++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto index 385524c42..b390acf53 100644 --- a/api/proto/banyandb/database/v1/schema.proto +++ b/api/proto/banyandb/database/v1/schema.proto @@ -96,6 +96,8 @@ message FieldSpec { EncodingMethod encoding_method = 3 [(validate.rules).enum.defined_only = true]; // compression_method indicates how to compress data during writing CompressionMethod compression_method = 4 [(validate.rules).enum.defined_only = true]; + // aggregate_function indicates how to aggregate data + model.v1.MeasureAggregate aggregate_function = 5; } // Measure intends to store data point @@ -115,6 +117,13 @@ message Measure { google.protobuf.Timestamp updated_at = 6; } +message MeasureAggregateFunction { + // type indicates the type of function argument + FieldType type = 1 [(validate.rules).enum.defined_only = true]; + // aggregate_function indicates specific function for measure data + model.v1.MeasureAggregate aggregate_function = 2; +} + // TopNAggregation generates offline TopN statistics for a measure's TopN approximation message TopNAggregation { // metadata is the identity of an aggregation diff --git a/docs/api-reference.md b/docs/api-reference.md index 75a6d911f..f50a82f3f 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -63,6 +63,7 @@ - [IndexRule](#banyandb-database-v1-IndexRule) - [IndexRuleBinding](#banyandb-database-v1-IndexRuleBinding) - [Measure](#banyandb-database-v1-Measure) + - [MeasureAggregateFunction](#banyandb-database-v1-MeasureAggregateFunction) - [Stream](#banyandb-database-v1-Stream) - [Subject](#banyandb-database-v1-Subject) - [TagFamilySpec](#banyandb-database-v1-TagFamilySpec) @@ -113,6 +114,8 @@ - [IndexRuleRegistryServiceListResponse](#banyandb-database-v1-IndexRuleRegistryServiceListResponse) - [IndexRuleRegistryServiceUpdateRequest](#banyandb-database-v1-IndexRuleRegistryServiceUpdateRequest) - [IndexRuleRegistryServiceUpdateResponse](#banyandb-database-v1-IndexRuleRegistryServiceUpdateResponse) + - [MeasureAggregateFunctionServiceSupportRequest](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportRequest) + - [MeasureAggregateFunctionServiceSupportResponse](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportResponse) - [MeasureRegistryServiceCreateRequest](#banyandb-database-v1-MeasureRegistryServiceCreateRequest) - [MeasureRegistryServiceCreateResponse](#banyandb-database-v1-MeasureRegistryServiceCreateResponse) - [MeasureRegistryServiceDeleteRequest](#banyandb-database-v1-MeasureRegistryServiceDeleteRequest) @@ -153,6 +156,7 @@ - [GroupRegistryService](#banyandb-database-v1-GroupRegistryService) - [IndexRuleBindingRegistryService](#banyandb-database-v1-IndexRuleBindingRegistryService) - [IndexRuleRegistryService](#banyandb-database-v1-IndexRuleRegistryService) + - [MeasureAggregateFunctionService](#banyandb-database-v1-MeasureAggregateFunctionService) - [MeasureRegistryService](#banyandb-database-v1-MeasureRegistryService) - [StreamRegistryService](#banyandb-database-v1-StreamRegistryService) - [TopNAggregationRegistryService](#banyandb-database-v1-TopNAggregationRegistryService) @@ -691,6 +695,28 @@ Trace is the top level message of a trace. | AGGREGATION_FUNCTION_SUM | 5 | | + + + +### MeasureAggregate + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| MEASURE_AGGREGATE_UNSPECIFIED | 0 | | +| MEASURE_AGGREGATE_MIN | 1 | Calculate the minimum value of delta measures. | +| MEASURE_AGGREGATE_MAX | 2 | Calculate the maximum value of delta measures. | +| MEASURE_AGGREGATE_COUNT | 3 | Count the number of delta measures. | +| MEASURE_AGGREGATE_SUM | 4 | Calculate the sum value of delta measures. | +| MEASURE_AGGREGATE_AVG | 5 | Calculate the average value of delta measures. | +| MEASURE_AGGREGATE_PERCENT | 6 | Calculate the percentage of delta measures, where the input matches with the condition. | +| MEASURE_AGGREGATE_RATE | 7 | Calculate the ratio for measures, where the input matches with the condition. | +| MEASURE_AGGREGATE_HISTOGRAM | 8 | Calculate the histogram for delta measures. | +| MEASURE_AGGREGATE_PERCENTILE2 | 9 | Calculate the {p99, p95, p90, p75, p50} for delta measures. | +| MEASURE_AGGREGATE_APDEX | 10 | Calculate the apdex for delta measures. | +| MEASURE_AGGREGATE_PERCENTILE | 11 | Same like PERCENTILE2, little different on algorithm. | + + @@ -955,6 +981,7 @@ FieldSpec is the specification of field | field_type | [FieldType](#banyandb-database-v1-FieldType) | | field_type denotes the type of field value | | encoding_method | [EncodingMethod](#banyandb-database-v1-EncodingMethod) | | encoding_method indicates how to encode data during writing | | compression_method | [CompressionMethod](#banyandb-database-v1-CompressionMethod) | | compression_method indicates how to compress data during writing | +| aggregate_function | [banyandb.model.v1.MeasureAggregate](#banyandb-model-v1-MeasureAggregate) | | aggregate_function indicates how to aggregate data | @@ -1024,6 +1051,22 @@ Measure intends to store data point + + +### MeasureAggregateFunction + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [FieldType](#banyandb-database-v1-FieldType) | | type indicates the type of function argument | +| aggregate_function | [banyandb.model.v1.MeasureAggregate](#banyandb-model-v1-MeasureAggregate) | | aggregate_function indicates specific function for measure data | + + + + + + ### Stream @@ -1718,6 +1761,31 @@ Type determine the index structure under the hood + + +### MeasureAggregateFunctionServiceSupportRequest + + + + + + + + + +### MeasureAggregateFunctionServiceSupportResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| measure_aggregate_function | [MeasureAggregateFunction](#banyandb-database-v1-MeasureAggregateFunction) | repeated | | + + + + + + ### MeasureRegistryServiceCreateRequest @@ -2302,6 +2370,16 @@ Type determine the index structure under the hood | Exist | [IndexRuleRegistryServiceExistRequest](#banyandb-database-v1-IndexRuleRegistryServiceExistRequest) | [IndexRuleRegistryServiceExistResponse](#banyandb-database-v1-IndexRuleRegistryServiceExistResponse) | Exist doesn't expose an HTTP endpoint. Please use HEAD method to touch Get instead | + + +### MeasureAggregateFunctionService + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| Support | [MeasureAggregateFunctionServiceSupportRequest](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportRequest) | [MeasureAggregateFunctionServiceSupportResponse](#banyandb-database-v1-MeasureAggregateFunctionServiceSupportResponse) | Support doesn't need metadata, it's static and stateless. | + + ### MeasureRegistryService From 420c4efa55721ef06cffb73935a71d44d609b268 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Mon, 26 Aug 2024 11:35:29 +0800 Subject: [PATCH 12/28] remove struct MAFArgument[T] --- .../measure/aggregate/aggregate_function.go | 13 +++----- banyand/measure/aggregate/avg.go | 4 +-- banyand/measure/aggregate/avg_test.go | 6 ++-- banyand/measure/aggregate/min.go | 2 +- banyand/measure/aggregate/min_test.go | 33 ++++++++++++------- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 3d61764c4..eca3f465a 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -31,7 +31,7 @@ type Void struct{} // MAFInput synchronizes with `pbv1.ValueType`, excluding `ValueTypeUnknown` // and `ValueTypeBinaryData`. type MAFInput interface { - ~string | ~int64 | ~float64 | ~[]string | ~[]int64 | Void + Void | ~string | ~int64 | ~float64 | ~[]string | ~[]int64 } // MAFKeep represents the only two types of value hold by MAF. @@ -41,16 +41,11 @@ type MAFKeep interface { var errFieldValueType = fmt.Errorf("unsupported input value type on this field") -// MAFArgument represents a segment of elements as an argument. -type MAFArgument[T MAFInput] struct { - elements []T -} - // MAFArguments represents the argument array, with at most three arguments. type MAFArguments[A, B, C MAFInput] struct { - arg0 MAFArgument[A] - arg1 MAFArgument[B] - arg2 MAFArgument[C] + arg0 []A + arg1 []B + arg2 []C } // MAF describes two stages of aggregation. diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index 247ac46ea..ab5072560 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -26,7 +26,7 @@ type Avg[A, B, C MAFInput, K MAFKeep] struct { // Combine takes elements to do the aggregation. // Avg uses type parameter A and B. func (a *Avg[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { - for _, arg0 := range arguments.arg0.elements { + for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: a.summation += K(arg0) @@ -41,7 +41,7 @@ func (a *Avg[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { } } - for _, arg1 := range arguments.arg1.elements { + for _, arg1 := range arguments.arg1 { switch arg1 := any(arg1).(type) { case int64: a.count += arg1 diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index 50a3d54ad..fd31d29e0 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -30,9 +30,9 @@ func TestAvg(t *testing.T) { avgInt64, _ := NewMeasureAggregateFunction[int64, int64, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) err = avgInt64.Combine(MAFArguments[int64, int64, Void]{ - arg0: MAFArgument[int64]{[]int64{1, 3, 3}}, // mock the "summation" column - arg1: MAFArgument[int64]{[]int64{1, 1, 1}}, // mock the "count" column - arg2: MAFArgument[Void]{}, + arg0: []int64{1, 3, 3}, // mock the "summation" column + arg1: []int64{1, 1, 1}, // mock the "count" column + arg2: nil, // mock an empty column }) assert.NoError(t, err) assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 817e23fac..74af775e1 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -25,7 +25,7 @@ type Min[A, B, C MAFInput, K MAFKeep] struct { // Combine takes elements to do the aggregation. // Min uses type parameter A. func (m *Min[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { - for _, arg0 := range arguments.arg0.elements { + for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: if K(arg0) < m.minimum { diff --git a/banyand/measure/aggregate/min_test.go b/banyand/measure/aggregate/min_test.go index 83b9b3cd4..c9e7e67b1 100644 --- a/banyand/measure/aggregate/min_test.go +++ b/banyand/measure/aggregate/min_test.go @@ -31,9 +31,9 @@ func TestMin(t *testing.T) { // case1: input int64 elements minInt64, _ := NewMeasureAggregateFunction[int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) err = minInt64.Combine(MAFArguments[int64, Void, Void]{ - arg0: MAFArgument[int64]{[]int64{1, 2, 3}}, - arg1: MAFArgument[Void]{}, - arg2: MAFArgument[Void]{}, + arg0: []int64{1, 2, 3}, + arg1: nil, + arg2: nil, }) assert.NoError(t, err) assert.Equal(t, int64(1), minInt64.Result()) @@ -41,9 +41,9 @@ func TestMin(t *testing.T) { // case2: input float64 elements minFloat64, _ := NewMeasureAggregateFunction[float64, Void, Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) err = minFloat64.Combine(MAFArguments[float64, Void, Void]{ - arg0: MAFArgument[float64]{[]float64{1.0, 2.0, 3.0}}, - arg1: MAFArgument[Void]{}, - arg2: MAFArgument[Void]{}, + arg0: []float64{1.0, 2.0, 3.0}, + arg1: nil, + arg2: nil, }) assert.NoError(t, err) assert.Equal(t, 1.0, minFloat64.Result()) @@ -51,9 +51,9 @@ func TestMin(t *testing.T) { // case3: input []int64 elements minInt64Arr, _ := NewMeasureAggregateFunction[[]int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) err = minInt64Arr.Combine(MAFArguments[[]int64, Void, Void]{ - arg0: MAFArgument[[]int64]{[][]int64{{1, 2}, {10, 20}}}, - arg1: MAFArgument[Void]{}, - arg2: MAFArgument[Void]{}, + arg0: [][]int64{{1, 2}, {10, 20}}, + arg1: nil, + arg2: nil, }) assert.NoError(t, err) assert.Equal(t, int64(1), minInt64Arr.Result()) @@ -62,9 +62,18 @@ func TestMin(t *testing.T) { minStr, _ := NewMeasureAggregateFunction[string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) err = minStr.Combine(MAFArguments[string, Void, Void]{ // fixme If there is no element, can't recognize the wrong input type. It needs at least one variable. - arg0: MAFArgument[string]{[]string{"a"}}, - arg1: MAFArgument[Void]{}, - arg2: MAFArgument[Void]{}, + arg0: []string{"a"}, + arg1: nil, + arg2: nil, }) assert.Errorf(t, err, errFieldValueType.Error()) + + // case5: input nothing, always OK + minStrArr, _ := NewMeasureAggregateFunction[[]string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minStrArr.Combine(MAFArguments[[]string, Void, Void]{ + arg0: [][]string{}, + arg1: nil, + arg2: nil, + }) + assert.NoError(t, err) } From ac23ca76f6c2bfdb4a81ee8953600fc82408c957 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 29 Aug 2024 10:39:22 +0800 Subject: [PATCH 13/28] rename Function --- .../measure/aggregate/aggregate_function.go | 37 ++++++++++--------- banyand/measure/aggregate/avg.go | 4 +- banyand/measure/aggregate/avg_test.go | 2 +- banyand/measure/aggregate/min.go | 4 +- banyand/measure/aggregate/min_test.go | 10 ++--- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index eca3f465a..7b47e8d45 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -25,43 +25,44 @@ import ( modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" ) -// Void type contains nothing. It works as a placeholder for type parameter. +// Void type contains nothing. It works as a placeholder for type parameters of `Arguments`. type Void struct{} -// MAFInput synchronizes with `pbv1.ValueType`, excluding `ValueTypeUnknown` -// and `ValueTypeBinaryData`. -type MAFInput interface { - Void | ~string | ~int64 | ~float64 | ~[]string | ~[]int64 +// Input covers possible types of Function's arguments. It synchronizes with +// `pbv1.ValueType`, excluding `ValueTypeUnknown` and `ValueTypeBinaryData`. +type Input interface { + Void | ~int64 | ~[]int64 | ~float64 | ~string | ~[]string } -// MAFKeep represents the only two types of value hold by MAF. -type MAFKeep interface { +// Output covers possible types of Function's return value. +// todo It doesn't cover string type. +type Output interface { ~int64 | ~float64 } var errFieldValueType = fmt.Errorf("unsupported input value type on this field") -// MAFArguments represents the argument array, with at most three arguments. -type MAFArguments[A, B, C MAFInput] struct { +// Arguments represents the argument array, with at most three arguments. +type Arguments[A, B, C Input] struct { arg0 []A arg1 []B arg2 []C } -// MAF describes two stages of aggregation. -type MAF[A, B, C MAFInput, K MAFKeep] interface { +// Function describes two stages of aggregation. +type Function[A, B, C Input, K Output] interface { // Combine takes elements to do the aggregation. // It uses a two-dimensional array to represent the argument array. - Combine(arguments MAFArguments[A, B, C]) error + Combine(arguments Arguments[A, B, C]) error // Result gives the result for the aggregation. // It uses "keep" value type to represent output value type. Result() K } -// NewMeasureAggregateFunction is the factory for MAF. -func NewMeasureAggregateFunction[A, B, C MAFInput, K MAFKeep](aggregate modelv1.MeasureAggregate) (MAF[A, B, C, K], error) { - var function MAF[A, B, C, K] +// NewMeasureAggregateFunction is the factory for Function. +func NewMeasureAggregateFunction[A, B, C Input, K Output](aggregate modelv1.MeasureAggregate) (Function[A, B, C, K], error) { + var function Function[A, B, C, K] switch aggregate { case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: function = &Min[A, B, C, K]{minimum: maxValue[K]()} @@ -74,17 +75,19 @@ func NewMeasureAggregateFunction[A, B, C MAFInput, K MAFKeep](aggregate modelv1. return function, nil } -func zeroValue[K MAFKeep]() K { +func zeroValue[K Output]() K { var z K return z } -func maxValue[K MAFKeep]() (r K) { +func maxValue[K Output]() (r K) { switch x := any(&r).(type) { case *int64: *x = math.MaxInt64 case *float64: *x = math.MaxFloat64 + case *string: + *x = "" default: panic("unreachable") } diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index ab5072560..210fd2263 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -18,14 +18,14 @@ package aggregate // Avg calculates the average value of elements. -type Avg[A, B, C MAFInput, K MAFKeep] struct { +type Avg[A, B, C Input, K Output] struct { summation K count int64 } // Combine takes elements to do the aggregation. // Avg uses type parameter A and B. -func (a *Avg[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { +func (a *Avg[A, B, C, K]) Combine(arguments Arguments[A, B, C]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index fd31d29e0..9df270df0 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -29,7 +29,7 @@ func TestAvg(t *testing.T) { var err error avgInt64, _ := NewMeasureAggregateFunction[int64, int64, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) - err = avgInt64.Combine(MAFArguments[int64, int64, Void]{ + err = avgInt64.Combine(Arguments[int64, int64, Void]{ arg0: []int64{1, 3, 3}, // mock the "summation" column arg1: []int64{1, 1, 1}, // mock the "count" column arg2: nil, // mock an empty column diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 74af775e1..57d68da50 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -18,13 +18,13 @@ package aggregate // Min calculates the minimum value of elements. -type Min[A, B, C MAFInput, K MAFKeep] struct { +type Min[A, B, C Input, K Output] struct { minimum K } // Combine takes elements to do the aggregation. // Min uses type parameter A. -func (m *Min[A, B, C, K]) Combine(arguments MAFArguments[A, B, C]) error { +func (m *Min[A, B, C, K]) Combine(arguments Arguments[A, B, C]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: diff --git a/banyand/measure/aggregate/min_test.go b/banyand/measure/aggregate/min_test.go index c9e7e67b1..f2ccc35b9 100644 --- a/banyand/measure/aggregate/min_test.go +++ b/banyand/measure/aggregate/min_test.go @@ -30,7 +30,7 @@ func TestMin(t *testing.T) { // case1: input int64 elements minInt64, _ := NewMeasureAggregateFunction[int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minInt64.Combine(MAFArguments[int64, Void, Void]{ + err = minInt64.Combine(Arguments[int64, Void, Void]{ arg0: []int64{1, 2, 3}, arg1: nil, arg2: nil, @@ -40,7 +40,7 @@ func TestMin(t *testing.T) { // case2: input float64 elements minFloat64, _ := NewMeasureAggregateFunction[float64, Void, Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minFloat64.Combine(MAFArguments[float64, Void, Void]{ + err = minFloat64.Combine(Arguments[float64, Void, Void]{ arg0: []float64{1.0, 2.0, 3.0}, arg1: nil, arg2: nil, @@ -50,7 +50,7 @@ func TestMin(t *testing.T) { // case3: input []int64 elements minInt64Arr, _ := NewMeasureAggregateFunction[[]int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minInt64Arr.Combine(MAFArguments[[]int64, Void, Void]{ + err = minInt64Arr.Combine(Arguments[[]int64, Void, Void]{ arg0: [][]int64{{1, 2}, {10, 20}}, arg1: nil, arg2: nil, @@ -60,7 +60,7 @@ func TestMin(t *testing.T) { // case4: unexpected input type minStr, _ := NewMeasureAggregateFunction[string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minStr.Combine(MAFArguments[string, Void, Void]{ + err = minStr.Combine(Arguments[string, Void, Void]{ // fixme If there is no element, can't recognize the wrong input type. It needs at least one variable. arg0: []string{"a"}, arg1: nil, @@ -70,7 +70,7 @@ func TestMin(t *testing.T) { // case5: input nothing, always OK minStrArr, _ := NewMeasureAggregateFunction[[]string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minStrArr.Combine(MAFArguments[[]string, Void, Void]{ + err = minStrArr.Combine(Arguments[[]string, Void, Void]{ arg0: [][]string{}, arg1: nil, arg2: nil, From 2b5ca525bc11550b3d23adc5e922a66b382446fe Mon Sep 17 00:00:00 2001 From: StLeoX Date: Fri, 30 Aug 2024 10:35:38 +0800 Subject: [PATCH 14/28] simplify Input and Arguments --- .../measure/aggregate/aggregate_function.go | 42 ++++++++---------- banyand/measure/aggregate/avg.go | 22 +++++----- banyand/measure/aggregate/avg_test.go | 15 +++++-- banyand/measure/aggregate/min.go | 22 ++++------ banyand/measure/aggregate/min_test.go | 43 +++---------------- 5 files changed, 55 insertions(+), 89 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 7b47e8d45..1fe99d6ea 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -28,46 +28,42 @@ import ( // Void type contains nothing. It works as a placeholder for type parameters of `Arguments`. type Void struct{} -// Input covers possible types of Function's arguments. It synchronizes with -// `pbv1.ValueType`, excluding `ValueTypeUnknown` and `ValueTypeBinaryData`. +// Input covers possible types of Function's arguments. It synchronizes with `FieldType` in schema. type Input interface { - Void | ~int64 | ~[]int64 | ~float64 | ~string | ~[]string + Void | ~int64 | ~float64 } // Output covers possible types of Function's return value. -// todo It doesn't cover string type. type Output interface { ~int64 | ~float64 } var errFieldValueType = fmt.Errorf("unsupported input value type on this field") -// Arguments represents the argument array, with at most three arguments. -type Arguments[A, B, C Input] struct { +// Arguments represents the argument array, with one argument or two arguments. +type Arguments[A, B Input] struct { arg0 []A arg1 []B - arg2 []C } // Function describes two stages of aggregation. -type Function[A, B, C Input, K Output] interface { +type Function[A, B Input, R Output] interface { // Combine takes elements to do the aggregation. // It uses a two-dimensional array to represent the argument array. - Combine(arguments Arguments[A, B, C]) error + Combine(arguments Arguments[A, B]) error // Result gives the result for the aggregation. - // It uses "keep" value type to represent output value type. - Result() K + Result() R } // NewMeasureAggregateFunction is the factory for Function. -func NewMeasureAggregateFunction[A, B, C Input, K Output](aggregate modelv1.MeasureAggregate) (Function[A, B, C, K], error) { - var function Function[A, B, C, K] +func NewMeasureAggregateFunction[A, B Input, R Output](aggregate modelv1.MeasureAggregate) (Function[A, B, R], error) { + var function Function[A, B, R] switch aggregate { case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: - function = &Min[A, B, C, K]{minimum: maxValue[K]()} + function = &Min[A, B, R]{minimum: maxValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: - function = &Avg[A, B, C, K]{summation: zeroValue[K](), count: 0} + function = &Avg[A, B, R]{summation: zeroValue[R](), count: 0} default: return nil, fmt.Errorf("MeasureAggregate unknown") } @@ -75,19 +71,19 @@ func NewMeasureAggregateFunction[A, B, C Input, K Output](aggregate modelv1.Meas return function, nil } -func zeroValue[K Output]() K { - var z K - return z +func zeroValue[R Output]() R { + var r R + return r } -func maxValue[K Output]() (r K) { - switch x := any(&r).(type) { +func maxValue[R Output]() (r R) { + switch a := any(&r).(type) { case *int64: - *x = math.MaxInt64 + *a = math.MaxInt64 case *float64: - *x = math.MaxFloat64 + *a = math.MaxFloat64 case *string: - *x = "" + *a = "" default: panic("unreachable") } diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index 210fd2263..cc1273604 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -18,24 +18,20 @@ package aggregate // Avg calculates the average value of elements. -type Avg[A, B, C Input, K Output] struct { - summation K +type Avg[A, B Input, R Output] struct { + summation R count int64 } // Combine takes elements to do the aggregation. // Avg uses type parameter A and B. -func (a *Avg[A, B, C, K]) Combine(arguments Arguments[A, B, C]) error { +func (a *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - a.summation += K(arg0) + a.summation += R(arg0) case float64: - a.summation += K(arg0) - case []int64: - for _, v := range arg0 { - a.summation += K(v) - } + a.summation += R(arg0) default: return errFieldValueType } @@ -54,12 +50,14 @@ func (a *Avg[A, B, C, K]) Combine(arguments Arguments[A, B, C]) error { } // Result gives the result for the aggregation. -func (a *Avg[A, B, C, K]) Result() K { +func (a *Avg[A, B, R]) Result() R { // In unusual situations it returns the zero value. if a.count == 0 { - return zeroValue[K]() + return zeroValue[R]() } // According to the semantics of GoLang, the division of one int by another int // returns an int, instead of a float. - return a.summation / K(a.count) + return a.summation / R(a.count) } + +// todo add NewAvgArg diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index 9df270df0..893819fae 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -28,12 +28,21 @@ import ( func TestAvg(t *testing.T) { var err error - avgInt64, _ := NewMeasureAggregateFunction[int64, int64, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) - err = avgInt64.Combine(Arguments[int64, int64, Void]{ + // case1: input int64 elements + avgInt64, _ := NewMeasureAggregateFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) + err = avgInt64.Combine(Arguments[int64, int64]{ arg0: []int64{1, 3, 3}, // mock the "summation" column arg1: []int64{1, 1, 1}, // mock the "count" column - arg2: nil, // mock an empty column }) assert.NoError(t, err) assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int + + // case2: input float64 elements + avgFloat64, _ := NewMeasureAggregateFunction[float64, int64, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) + err = avgFloat64.Combine(Arguments[float64, int64]{ + arg0: []float64{1.0, 3.0, 3.0}, // mock the "summation" column + arg1: []int64{1, 1, 1}, // mock the "count" column + }) + assert.NoError(t, err) + assert.Equal(t, 7.0/3, avgFloat64.Result()) } diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 57d68da50..ecbd23789 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -18,28 +18,22 @@ package aggregate // Min calculates the minimum value of elements. -type Min[A, B, C Input, K Output] struct { - minimum K +type Min[A, B Input, R Output] struct { + minimum R } // Combine takes elements to do the aggregation. // Min uses type parameter A. -func (m *Min[A, B, C, K]) Combine(arguments Arguments[A, B, C]) error { +func (m *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - if K(arg0) < m.minimum { - m.minimum = K(arg0) + if R(arg0) < m.minimum { + m.minimum = R(arg0) } case float64: - if K(arg0) < m.minimum { - m.minimum = K(arg0) - } - case []int64: - for _, v := range arg0 { - if K(v) < m.minimum { - m.minimum = K(v) - } + if R(arg0) < m.minimum { + m.minimum = R(arg0) } default: return errFieldValueType @@ -49,6 +43,6 @@ func (m *Min[A, B, C, K]) Combine(arguments Arguments[A, B, C]) error { } // Result gives the result for the aggregation. -func (m *Min[A, B, C, K]) Result() K { +func (m *Min[A, B, R]) Result() R { return m.minimum } diff --git a/banyand/measure/aggregate/min_test.go b/banyand/measure/aggregate/min_test.go index f2ccc35b9..1f595ca21 100644 --- a/banyand/measure/aggregate/min_test.go +++ b/banyand/measure/aggregate/min_test.go @@ -29,51 +29,20 @@ func TestMin(t *testing.T) { var err error // case1: input int64 elements - minInt64, _ := NewMeasureAggregateFunction[int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minInt64.Combine(Arguments[int64, Void, Void]{ - arg0: []int64{1, 2, 3}, + minInt64, _ := NewMeasureAggregateFunction[int64, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minInt64.Combine(Arguments[int64, Void]{ + arg0: []int64{1, 2, 3}, // mock the "minimum" column arg1: nil, - arg2: nil, }) assert.NoError(t, err) assert.Equal(t, int64(1), minInt64.Result()) // case2: input float64 elements - minFloat64, _ := NewMeasureAggregateFunction[float64, Void, Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minFloat64.Combine(Arguments[float64, Void, Void]{ - arg0: []float64{1.0, 2.0, 3.0}, + minFloat64, _ := NewMeasureAggregateFunction[float64, Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minFloat64.Combine(Arguments[float64, Void]{ + arg0: []float64{1.0, 2.0, 3.0}, // mock the "minimum" column arg1: nil, - arg2: nil, }) assert.NoError(t, err) assert.Equal(t, 1.0, minFloat64.Result()) - - // case3: input []int64 elements - minInt64Arr, _ := NewMeasureAggregateFunction[[]int64, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minInt64Arr.Combine(Arguments[[]int64, Void, Void]{ - arg0: [][]int64{{1, 2}, {10, 20}}, - arg1: nil, - arg2: nil, - }) - assert.NoError(t, err) - assert.Equal(t, int64(1), minInt64Arr.Result()) - - // case4: unexpected input type - minStr, _ := NewMeasureAggregateFunction[string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minStr.Combine(Arguments[string, Void, Void]{ - // fixme If there is no element, can't recognize the wrong input type. It needs at least one variable. - arg0: []string{"a"}, - arg1: nil, - arg2: nil, - }) - assert.Errorf(t, err, errFieldValueType.Error()) - - // case5: input nothing, always OK - minStrArr, _ := NewMeasureAggregateFunction[[]string, Void, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minStrArr.Combine(Arguments[[]string, Void, Void]{ - arg0: [][]string{}, - arg1: nil, - arg2: nil, - }) - assert.NoError(t, err) } From f6b6fe6a403bb2f42618c1e241e7b5c5089ad4e6 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Fri, 30 Aug 2024 11:42:59 +0800 Subject: [PATCH 15/28] add NewArguments --- .../measure/aggregate/aggregate_function.go | 6 ++--- banyand/measure/aggregate/avg.go | 8 +++++- banyand/measure/aggregate/avg_test.go | 25 ++++++++++--------- banyand/measure/aggregate/min.go | 8 ++++++ banyand/measure/aggregate/min_test.go | 25 +++++++++---------- 5 files changed, 43 insertions(+), 29 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 1fe99d6ea..69427c94f 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -56,10 +56,10 @@ type Function[A, B Input, R Output] interface { Result() R } -// NewMeasureAggregateFunction is the factory for Function. -func NewMeasureAggregateFunction[A, B Input, R Output](aggregate modelv1.MeasureAggregate) (Function[A, B, R], error) { +// NewFunction constructs the aggregate function with given kind and parameter types. +func NewFunction[A, B Input, R Output](kind modelv1.MeasureAggregate) (Function[A, B, R], error) { var function Function[A, B, R] - switch aggregate { + switch kind { case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: function = &Min[A, B, R]{minimum: maxValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index cc1273604..c2fccf0cd 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -60,4 +60,10 @@ func (a *Avg[A, B, R]) Result() R { return a.summation / R(a.count) } -// todo add NewAvgArg +// NewAvgArguments constructs arguments. +func NewAvgArguments[A Input](a []A, b []int64) Arguments[A, int64] { + return Arguments[A, int64]{ + arg0: a, + arg1: b, + } +} diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index 893819fae..34801eb12 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package aggregate +package aggregate_test import ( "testing" @@ -23,26 +23,27 @@ import ( "github.com/stretchr/testify/assert" modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" ) func TestAvg(t *testing.T) { var err error - // case1: input int64 elements - avgInt64, _ := NewMeasureAggregateFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) - err = avgInt64.Combine(Arguments[int64, int64]{ - arg0: []int64{1, 3, 3}, // mock the "summation" column - arg1: []int64{1, 1, 1}, // mock the "count" column - }) + // case1: input int64 values + avgInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) + err = avgInt64.Combine(aggregate.NewAvgArguments[int64]( + []int64{1, 2, 3}, // mock the "summation" column + []int64{1, 1, 1}, // mock the "count" column + )) assert.NoError(t, err) assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int // case2: input float64 elements - avgFloat64, _ := NewMeasureAggregateFunction[float64, int64, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) - err = avgFloat64.Combine(Arguments[float64, int64]{ - arg0: []float64{1.0, 3.0, 3.0}, // mock the "summation" column - arg1: []int64{1, 1, 1}, // mock the "count" column - }) + avgFloat64, _ := aggregate.NewFunction[float64, int64, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) + err = avgFloat64.Combine(aggregate.NewAvgArguments[float64]( + []float64{1.0, 3.0, 3.0}, // mock the "summation" column + []int64{1, 1, 1}, // mock the "count" column + )) assert.NoError(t, err) assert.Equal(t, 7.0/3, avgFloat64.Result()) } diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index ecbd23789..2caacd471 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -46,3 +46,11 @@ func (m *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { func (m *Min[A, B, R]) Result() R { return m.minimum } + +// NewMinArguments constructs arguments. +func NewMinArguments[A Input](a []A) Arguments[A, Void] { + return Arguments[A, Void]{ + arg0: a, + arg1: nil, + } +} diff --git a/banyand/measure/aggregate/min_test.go b/banyand/measure/aggregate/min_test.go index 1f595ca21..fa6fd9499 100644 --- a/banyand/measure/aggregate/min_test.go +++ b/banyand/measure/aggregate/min_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package aggregate +package aggregate_test import ( "testing" @@ -23,26 +23,25 @@ import ( "github.com/stretchr/testify/assert" modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" ) func TestMin(t *testing.T) { var err error - // case1: input int64 elements - minInt64, _ := NewMeasureAggregateFunction[int64, Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minInt64.Combine(Arguments[int64, Void]{ - arg0: []int64{1, 2, 3}, // mock the "minimum" column - arg1: nil, - }) + // case1: input int64 values + minInt64, _ := aggregate.NewFunction[int64, aggregate.Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minInt64.Combine(aggregate.NewMinArguments[int64]( + []int64{1, 2, 3}, // mock the "minimum" column + )) assert.NoError(t, err) assert.Equal(t, int64(1), minInt64.Result()) - // case2: input float64 elements - minFloat64, _ := NewMeasureAggregateFunction[float64, Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) - err = minFloat64.Combine(Arguments[float64, Void]{ - arg0: []float64{1.0, 2.0, 3.0}, // mock the "minimum" column - arg1: nil, - }) + // case2: input float64 values + minFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) + err = minFloat64.Combine(aggregate.NewMinArguments[float64]( + []float64{1.0, 2.0, 3.0}, // mock the "minimum" column + )) assert.NoError(t, err) assert.Equal(t, 1.0, minFloat64.Result()) } From 1098f25229b572cc69767366fc6ad83625010d20 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 3 Sep 2024 18:56:43 +0800 Subject: [PATCH 16/28] supports `Max` --- .../measure/aggregate/aggregate_function.go | 16 ++++++ banyand/measure/aggregate/max.go | 56 +++++++++++++++++++ banyand/measure/aggregate/max_test.go | 47 ++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 banyand/measure/aggregate/max.go create mode 100644 banyand/measure/aggregate/max_test.go diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 69427c94f..34ed401ea 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -62,6 +62,8 @@ func NewFunction[A, B Input, R Output](kind modelv1.MeasureAggregate) (Function[ switch kind { case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN: function = &Min[A, B, R]{minimum: maxValue[R]()} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MAX: + function = &Max[A, B, R]{maximum: minValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: function = &Avg[A, B, R]{summation: zeroValue[R](), count: 0} default: @@ -76,6 +78,20 @@ func zeroValue[R Output]() R { return r } +func minValue[R Output]() (r R) { + switch a := any(&r).(type) { + case *int64: + *a = math.MinInt64 + case *float64: + *a = -math.MaxFloat64 + case *string: + *a = "" + default: + panic("unreachable") + } + return +} + func maxValue[R Output]() (r R) { switch a := any(&r).(type) { case *int64: diff --git a/banyand/measure/aggregate/max.go b/banyand/measure/aggregate/max.go new file mode 100644 index 000000000..217213a05 --- /dev/null +++ b/banyand/measure/aggregate/max.go @@ -0,0 +1,56 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Max calculates the maximum value of elements. +type Max[A, B Input, R Output] struct { + maximum R +} + +// Combine takes elements to do the aggregation. +// Max uses type parameter A. +func (m *Max[A, B, R]) Combine(arguments Arguments[A, B]) error { + for _, arg0 := range arguments.arg0 { + switch arg0 := any(arg0).(type) { + case int64: + if R(arg0) > m.maximum { + m.maximum = R(arg0) + } + case float64: + if R(arg0) > m.maximum { + m.maximum = R(arg0) + } + default: + return errFieldValueType + } + } + return nil +} + +// Result gives the result for the aggregation. +func (m *Max[A, B, R]) Result() R { + return m.maximum +} + +// NewMaxArguments constructs arguments. +func NewMaxArguments[A Input](a []A) Arguments[A, Void] { + return Arguments[A, Void]{ + arg0: a, + arg1: nil, + } +} diff --git a/banyand/measure/aggregate/max_test.go b/banyand/measure/aggregate/max_test.go new file mode 100644 index 000000000..361877174 --- /dev/null +++ b/banyand/measure/aggregate/max_test.go @@ -0,0 +1,47 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" +) + +func TestMax(t *testing.T) { + var err error + + // case1: input int64 values + maxInt64, _ := aggregate.NewFunction[int64, aggregate.Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MAX) + err = maxInt64.Combine(aggregate.NewMaxArguments[int64]( + []int64{1, 2, 3}, // mock the "maximum" column + )) + assert.NoError(t, err) + assert.Equal(t, int64(3), maxInt64.Result()) + + // case2: input float64 values + maxFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MAX) + err = maxFloat64.Combine(aggregate.NewMaxArguments[float64]( + []float64{1.0, 2.0, 3.0}, // mock the "maximum" column + )) + assert.NoError(t, err) + assert.Equal(t, 3.0, maxFloat64.Result()) +} From aa722cd4d804b5c045274b71c7a7d0f322b026f8 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 3 Sep 2024 19:11:30 +0800 Subject: [PATCH 17/28] supports `Count` --- .../measure/aggregate/aggregate_function.go | 2 + banyand/measure/aggregate/count.go | 50 +++++++++++++++++++ banyand/measure/aggregate/count_test.go | 40 +++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 banyand/measure/aggregate/count.go create mode 100644 banyand/measure/aggregate/count_test.go diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 34ed401ea..5aa0fce9f 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -64,6 +64,8 @@ func NewFunction[A, B Input, R Output](kind modelv1.MeasureAggregate) (Function[ function = &Min[A, B, R]{minimum: maxValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_MAX: function = &Max[A, B, R]{maximum: minValue[R]()} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_COUNT: + function = &Count[A, B, R]{count: 0} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: function = &Avg[A, B, R]{summation: zeroValue[R](), count: 0} default: diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go new file mode 100644 index 000000000..d4c0cf386 --- /dev/null +++ b/banyand/measure/aggregate/count.go @@ -0,0 +1,50 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Count calculates the count value of elements. +type Count[A, B Input, R Output] struct { + count int64 +} + +// Combine takes elements to do the aggregation. +// Count uses type parameter A. +func (m *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { + for _, arg0 := range arguments.arg0 { + switch arg0 := any(arg0).(type) { + case int64: + m.count += arg0 + default: + return errFieldValueType + } + } + return nil +} + +// Result gives the result for the aggregation. +func (m *Count[A, B, R]) Result() R { + return R(m.count) +} + +// NewCountArguments constructs arguments. +func NewCountArguments(a []int64) Arguments[int64, Void] { + return Arguments[int64, Void]{ + arg0: a, + arg1: nil, + } +} diff --git a/banyand/measure/aggregate/count_test.go b/banyand/measure/aggregate/count_test.go new file mode 100644 index 000000000..609855d90 --- /dev/null +++ b/banyand/measure/aggregate/count_test.go @@ -0,0 +1,40 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" +) + +func TestCount(t *testing.T) { + var err error + + // case1: input int64 values + countInt64, _ := aggregate.NewFunction[int64, aggregate.Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_COUNT) + err = countInt64.Combine(aggregate.NewCountArguments( + []int64{1, 2, 3}, // mock the "count" column + )) + assert.NoError(t, err) + assert.Equal(t, int64(6), countInt64.Result()) + +} From 337e2fc731b470e12ae34d304dc5aee2cf4eb3ff Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 3 Sep 2024 19:17:31 +0800 Subject: [PATCH 18/28] supports `Sum` --- .../measure/aggregate/aggregate_function.go | 2 + banyand/measure/aggregate/sum.go | 55 +++++++++++++++++++ banyand/measure/aggregate/sum_test.go | 47 ++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 banyand/measure/aggregate/sum.go create mode 100644 banyand/measure/aggregate/sum_test.go diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 5aa0fce9f..860e66641 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -66,6 +66,8 @@ func NewFunction[A, B Input, R Output](kind modelv1.MeasureAggregate) (Function[ function = &Max[A, B, R]{maximum: minValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_COUNT: function = &Count[A, B, R]{count: 0} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM: + function = &Sum[A, B, R]{summation: zeroValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: function = &Avg[A, B, R]{summation: zeroValue[R](), count: 0} default: diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go new file mode 100644 index 000000000..b49e1bbfd --- /dev/null +++ b/banyand/measure/aggregate/sum.go @@ -0,0 +1,55 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Sum calculates the summation value of elements. +type Sum[A, B Input, R Output] struct { + summation R +} + +// Combine takes elements to do the aggregation. +// Sum uses type parameter A. +func (a *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { + for _, arg0 := range arguments.arg0 { + switch arg0 := any(arg0).(type) { + case int64: + a.summation += R(arg0) + case float64: + a.summation += R(arg0) + default: + return errFieldValueType + } + } + + return nil +} + +// Result gives the result for the aggregation. +func (a *Sum[A, B, R]) Result() R { + // According to the semantics of GoLang, the division of one int by another int + // returns an int, instead of a float. + return a.summation +} + +// NewSumArguments constructs arguments. +func NewSumArguments[A Input](a []A) Arguments[A, Void] { + return Arguments[A, Void]{ + arg0: a, + arg1: nil, + } +} diff --git a/banyand/measure/aggregate/sum_test.go b/banyand/measure/aggregate/sum_test.go new file mode 100644 index 000000000..ff47e1443 --- /dev/null +++ b/banyand/measure/aggregate/sum_test.go @@ -0,0 +1,47 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" +) + +func TestSum(t *testing.T) { + var err error + + // case1: input int64 values + sumInt64, _ := aggregate.NewFunction[int64, aggregate.Void, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) + err = sumInt64.Combine(aggregate.NewSumArguments[int64]( + []int64{1, 2, 3}, // mock the "summation" column + )) + assert.NoError(t, err) + assert.Equal(t, int64(6), sumInt64.Result()) + + // case2: input float64 values + sumFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) + err = sumFloat64.Combine(aggregate.NewSumArguments[float64]( + []float64{1.0, 2.0, 3.0}, // mock the "summation" column + )) + assert.NoError(t, err) + assert.Equal(t, 6.0, sumFloat64.Result()) +} From 7de26d8060a59ba6ac81cd15dcd0e1d2f41a4197 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 3 Sep 2024 19:46:43 +0800 Subject: [PATCH 19/28] supports `Percent` --- .../measure/aggregate/aggregate_function.go | 2 + banyand/measure/aggregate/percent.go | 66 +++++++++++++++++++ banyand/measure/aggregate/percent_test.go | 41 ++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 banyand/measure/aggregate/percent.go create mode 100644 banyand/measure/aggregate/percent_test.go diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 860e66641..00358289f 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -70,6 +70,8 @@ func NewFunction[A, B Input, R Output](kind modelv1.MeasureAggregate) (Function[ function = &Sum[A, B, R]{summation: zeroValue[R]()} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG: function = &Avg[A, B, R]{summation: zeroValue[R](), count: 0} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT: + function = &Percent[A, B, R]{total: 0, match: 0} default: return nil, fmt.Errorf("MeasureAggregate unknown") } diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go new file mode 100644 index 000000000..694a1f25a --- /dev/null +++ b/banyand/measure/aggregate/percent.go @@ -0,0 +1,66 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Percent calculates the average value of elements. +type Percent[A, B Input, R Output] struct { + total int64 + match int64 +} + +// Combine takes elements to do the aggregation. +// Percent uses type parameter A and B. +func (a *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { + for _, arg0 := range arguments.arg0 { + switch arg0 := any(arg0).(type) { + case int64: + a.total += arg0 + default: + return errFieldValueType + } + } + + for _, arg1 := range arguments.arg1 { + switch arg1 := any(arg1).(type) { + case int64: + a.match += arg1 + default: + return errFieldValueType + } + } + + return nil +} + +// Result gives the result for the aggregation. +func (a *Percent[A, B, R]) Result() R { + // In unusual situations it returns the zero value. + if a.total == 0 { + return zeroValue[R]() + } + // Factory 100 is used to improve accuracy. + return R(a.match) * 100 / R(a.total) +} + +// NewPercentArguments constructs arguments. +func NewPercentArguments(a []int64, b []int64) Arguments[int64, int64] { + return Arguments[int64, int64]{ + arg0: a, + arg1: b, + } +} diff --git a/banyand/measure/aggregate/percent_test.go b/banyand/measure/aggregate/percent_test.go new file mode 100644 index 000000000..1203b403e --- /dev/null +++ b/banyand/measure/aggregate/percent_test.go @@ -0,0 +1,41 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" +) + +func TestPercent(t *testing.T) { + var err error + + // case1: input int64 values + PercentInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT) + err = PercentInt64.Combine(aggregate.NewPercentArguments( + []int64{10, 100, 1000}, // mock the "total" column + []int64{1, 10, 100}, // mock the "match" column + )) + assert.NoError(t, err) + assert.Equal(t, int64(10), PercentInt64.Result()) + +} From fde584c85a925de48a98795be9f74baf8d401a37 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 3 Sep 2024 19:53:49 +0800 Subject: [PATCH 20/28] supports `Rate` --- .../measure/aggregate/aggregate_function.go | 2 + banyand/measure/aggregate/rate.go | 66 +++++++++++++++++++ banyand/measure/aggregate/rate_test.go | 41 ++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 banyand/measure/aggregate/rate.go create mode 100644 banyand/measure/aggregate/rate_test.go diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 00358289f..6adbaa14d 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -72,6 +72,8 @@ func NewFunction[A, B Input, R Output](kind modelv1.MeasureAggregate) (Function[ function = &Avg[A, B, R]{summation: zeroValue[R](), count: 0} case modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT: function = &Percent[A, B, R]{total: 0, match: 0} + case modelv1.MeasureAggregate_MEASURE_AGGREGATE_RATE: + function = &Rate[A, B, R]{denominator: 0, numerator: 0} default: return nil, fmt.Errorf("MeasureAggregate unknown") } diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go new file mode 100644 index 000000000..d20db2bb8 --- /dev/null +++ b/banyand/measure/aggregate/rate.go @@ -0,0 +1,66 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate + +// Rate calculates the average value of elements. +type Rate[A, B Input, R Output] struct { + denominator int64 + numerator int64 +} + +// Combine takes elements to do the aggregation. +// Rate uses type parameter A and B. +func (a *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { + for _, arg0 := range arguments.arg0 { + switch arg0 := any(arg0).(type) { + case int64: + a.denominator += arg0 + default: + return errFieldValueType + } + } + + for _, arg1 := range arguments.arg1 { + switch arg1 := any(arg1).(type) { + case int64: + a.numerator += arg1 + default: + return errFieldValueType + } + } + + return nil +} + +// Result gives the result for the aggregation. +func (a *Rate[A, B, R]) Result() R { + // In unusual situations it returns the zero value. + if a.denominator == 0 { + return zeroValue[R]() + } + // Factory 100 is used to improve accuracy. + return R(a.numerator) * 100 / R(a.denominator) +} + +// NewRateArguments constructs arguments. +func NewRateArguments(a []int64, b []int64) Arguments[int64, int64] { + return Arguments[int64, int64]{ + arg0: a, + arg1: b, + } +} diff --git a/banyand/measure/aggregate/rate_test.go b/banyand/measure/aggregate/rate_test.go new file mode 100644 index 000000000..b4c09c200 --- /dev/null +++ b/banyand/measure/aggregate/rate_test.go @@ -0,0 +1,41 @@ +// Licensed to Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Apache Software Foundation (ASF) licenses this file to you 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 aggregate_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1" + "github.com/apache/skywalking-banyandb/banyand/measure/aggregate" +) + +func TestRate(t *testing.T) { + var err error + + // case1: input int64 values + rateInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_RATE) + err = rateInt64.Combine(aggregate.NewRateArguments( + []int64{10, 100, 1000}, // mock the "denominator" column + []int64{1, 10, 100}, // mock the "numerator" column + )) + assert.NoError(t, err) + assert.Equal(t, int64(10), rateInt64.Result()) + +} From 792dc1b3c8afa31a4ad6e8e15e55af35b606aa14 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 3 Sep 2024 19:57:26 +0800 Subject: [PATCH 21/28] add comments --- banyand/measure/aggregate/avg.go | 18 +++++++++--------- banyand/measure/aggregate/count.go | 10 +++++----- banyand/measure/aggregate/max.go | 14 +++++++------- banyand/measure/aggregate/min.go | 14 +++++++------- banyand/measure/aggregate/percent.go | 14 +++++++------- banyand/measure/aggregate/rate.go | 14 +++++++------- banyand/measure/aggregate/sum.go | 12 ++++++------ 7 files changed, 48 insertions(+), 48 deletions(-) diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index c2fccf0cd..7abc686da 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -24,14 +24,14 @@ type Avg[A, B Input, R Output] struct { } // Combine takes elements to do the aggregation. -// Avg uses type parameter A and B. -func (a *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { +// Avg uses type parameter A. +func (f *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - a.summation += R(arg0) + f.summation += R(arg0) case float64: - a.summation += R(arg0) + f.summation += R(arg0) default: return errFieldValueType } @@ -40,7 +40,7 @@ func (a *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg1 := range arguments.arg1 { switch arg1 := any(arg1).(type) { case int64: - a.count += arg1 + f.count += arg1 default: return errFieldValueType } @@ -50,14 +50,14 @@ func (a *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (a *Avg[A, B, R]) Result() R { +func (f *Avg[A, B, R]) Result() R { // In unusual situations it returns the zero value. - if a.count == 0 { + if f.count == 0 { return zeroValue[R]() } // According to the semantics of GoLang, the division of one int by another int - // returns an int, instead of a float. - return a.summation / R(a.count) + // returns an int, instead of f float. + return f.summation / R(f.count) } // NewAvgArguments constructs arguments. diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go index d4c0cf386..0b2daccd4 100644 --- a/banyand/measure/aggregate/count.go +++ b/banyand/measure/aggregate/count.go @@ -23,12 +23,12 @@ type Count[A, B Input, R Output] struct { } // Combine takes elements to do the aggregation. -// Count uses type parameter A. -func (m *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { +// Count uses none of type parameters. +func (f *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - m.count += arg0 + f.count += arg0 default: return errFieldValueType } @@ -37,8 +37,8 @@ func (m *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (m *Count[A, B, R]) Result() R { - return R(m.count) +func (f *Count[A, B, R]) Result() R { + return R(f.count) } // NewCountArguments constructs arguments. diff --git a/banyand/measure/aggregate/max.go b/banyand/measure/aggregate/max.go index 217213a05..5879435af 100644 --- a/banyand/measure/aggregate/max.go +++ b/banyand/measure/aggregate/max.go @@ -24,16 +24,16 @@ type Max[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Max uses type parameter A. -func (m *Max[A, B, R]) Combine(arguments Arguments[A, B]) error { +func (f *Max[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - if R(arg0) > m.maximum { - m.maximum = R(arg0) + if R(arg0) > f.maximum { + f.maximum = R(arg0) } case float64: - if R(arg0) > m.maximum { - m.maximum = R(arg0) + if R(arg0) > f.maximum { + f.maximum = R(arg0) } default: return errFieldValueType @@ -43,8 +43,8 @@ func (m *Max[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (m *Max[A, B, R]) Result() R { - return m.maximum +func (f *Max[A, B, R]) Result() R { + return f.maximum } // NewMaxArguments constructs arguments. diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 2caacd471..ab4576345 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -24,16 +24,16 @@ type Min[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Min uses type parameter A. -func (m *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { +func (f *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - if R(arg0) < m.minimum { - m.minimum = R(arg0) + if R(arg0) < f.minimum { + f.minimum = R(arg0) } case float64: - if R(arg0) < m.minimum { - m.minimum = R(arg0) + if R(arg0) < f.minimum { + f.minimum = R(arg0) } default: return errFieldValueType @@ -43,8 +43,8 @@ func (m *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (m *Min[A, B, R]) Result() R { - return m.minimum +func (f *Min[A, B, R]) Result() R { + return f.minimum } // NewMinArguments constructs arguments. diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index 694a1f25a..c39d74b9f 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -24,12 +24,12 @@ type Percent[A, B Input, R Output] struct { } // Combine takes elements to do the aggregation. -// Percent uses type parameter A and B. -func (a *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { +// Percent uses none of type parameters. +func (f *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - a.total += arg0 + f.total += arg0 default: return errFieldValueType } @@ -38,7 +38,7 @@ func (a *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg1 := range arguments.arg1 { switch arg1 := any(arg1).(type) { case int64: - a.match += arg1 + f.match += arg1 default: return errFieldValueType } @@ -48,13 +48,13 @@ func (a *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (a *Percent[A, B, R]) Result() R { +func (f *Percent[A, B, R]) Result() R { // In unusual situations it returns the zero value. - if a.total == 0 { + if f.total == 0 { return zeroValue[R]() } // Factory 100 is used to improve accuracy. - return R(a.match) * 100 / R(a.total) + return R(f.match) * 100 / R(f.total) } // NewPercentArguments constructs arguments. diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index d20db2bb8..7b0241811 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -24,12 +24,12 @@ type Rate[A, B Input, R Output] struct { } // Combine takes elements to do the aggregation. -// Rate uses type parameter A and B. -func (a *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { +// Rate uses none of type parameters. +func (f *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - a.denominator += arg0 + f.denominator += arg0 default: return errFieldValueType } @@ -38,7 +38,7 @@ func (a *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg1 := range arguments.arg1 { switch arg1 := any(arg1).(type) { case int64: - a.numerator += arg1 + f.numerator += arg1 default: return errFieldValueType } @@ -48,13 +48,13 @@ func (a *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (a *Rate[A, B, R]) Result() R { +func (f *Rate[A, B, R]) Result() R { // In unusual situations it returns the zero value. - if a.denominator == 0 { + if f.denominator == 0 { return zeroValue[R]() } // Factory 100 is used to improve accuracy. - return R(a.numerator) * 100 / R(a.denominator) + return R(f.numerator) * 100 / R(f.denominator) } // NewRateArguments constructs arguments. diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index b49e1bbfd..2f8ddb5b3 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -24,13 +24,13 @@ type Sum[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Sum uses type parameter A. -func (a *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { +func (f *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { switch arg0 := any(arg0).(type) { case int64: - a.summation += R(arg0) + f.summation += R(arg0) case float64: - a.summation += R(arg0) + f.summation += R(arg0) default: return errFieldValueType } @@ -40,10 +40,10 @@ func (a *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (a *Sum[A, B, R]) Result() R { +func (f *Sum[A, B, R]) Result() R { // According to the semantics of GoLang, the division of one int by another int - // returns an int, instead of a float. - return a.summation + // returns an int, instead of f float. + return f.summation } // NewSumArguments constructs arguments. From 806b6d929edd91a76cc93e875ecf3694699f4ef1 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 5 Sep 2024 14:30:57 +0800 Subject: [PATCH 22/28] fix unit test --- banyand/measure/aggregate/count_test.go | 1 - banyand/measure/aggregate/percent.go | 5 +++-- banyand/measure/aggregate/percent_test.go | 3 +-- banyand/measure/aggregate/rate.go | 4 ++-- banyand/measure/aggregate/rate_test.go | 3 +-- banyand/measure/aggregate/sum.go | 2 -- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/banyand/measure/aggregate/count_test.go b/banyand/measure/aggregate/count_test.go index 609855d90..db6a0b30c 100644 --- a/banyand/measure/aggregate/count_test.go +++ b/banyand/measure/aggregate/count_test.go @@ -36,5 +36,4 @@ func TestCount(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, int64(6), countInt64.Result()) - } diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index c39d74b9f..4dd425937 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -53,8 +53,9 @@ func (f *Percent[A, B, R]) Result() R { if f.total == 0 { return zeroValue[R]() } - // Factory 100 is used to improve accuracy. - return R(f.match) * 100 / R(f.total) + // Factory 10000 is used to improve accuracy. This factory is same as OAP. + // For example, "10 percent" will return 1000. + return R(f.match) * 10000 / R(f.total) } // NewPercentArguments constructs arguments. diff --git a/banyand/measure/aggregate/percent_test.go b/banyand/measure/aggregate/percent_test.go index 1203b403e..eb24a9b67 100644 --- a/banyand/measure/aggregate/percent_test.go +++ b/banyand/measure/aggregate/percent_test.go @@ -36,6 +36,5 @@ func TestPercent(t *testing.T) { []int64{1, 10, 100}, // mock the "match" column )) assert.NoError(t, err) - assert.Equal(t, int64(10), PercentInt64.Result()) - + assert.Equal(t, int64(1000), PercentInt64.Result()) } diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index 7b0241811..a0a0dc9a3 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -53,8 +53,8 @@ func (f *Rate[A, B, R]) Result() R { if f.denominator == 0 { return zeroValue[R]() } - // Factory 100 is used to improve accuracy. - return R(f.numerator) * 100 / R(f.denominator) + // Factory 10000 is used to improve accuracy. This factory is same as OAP. + return R(f.numerator) * 10000 / R(f.denominator) } // NewRateArguments constructs arguments. diff --git a/banyand/measure/aggregate/rate_test.go b/banyand/measure/aggregate/rate_test.go index b4c09c200..e03989f88 100644 --- a/banyand/measure/aggregate/rate_test.go +++ b/banyand/measure/aggregate/rate_test.go @@ -36,6 +36,5 @@ func TestRate(t *testing.T) { []int64{1, 10, 100}, // mock the "numerator" column )) assert.NoError(t, err) - assert.Equal(t, int64(10), rateInt64.Result()) - + assert.Equal(t, int64(1000), rateInt64.Result()) } diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index 2f8ddb5b3..96e418e95 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -41,8 +41,6 @@ func (f *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { // Result gives the result for the aggregation. func (f *Sum[A, B, R]) Result() R { - // According to the semantics of GoLang, the division of one int by another int - // returns an int, instead of f float. return f.summation } From b3127eb835b2778a13501f9013e8829905a6fedd Mon Sep 17 00:00:00 2001 From: StLeoX Date: Mon, 9 Sep 2024 20:48:45 +0800 Subject: [PATCH 23/28] removes dynamic type assertion --- banyand/measure/aggregate/aggregate_function.go | 10 +++++----- banyand/measure/aggregate/avg.go | 16 ++-------------- banyand/measure/aggregate/count.go | 7 +------ banyand/measure/aggregate/max.go | 13 ++----------- banyand/measure/aggregate/min.go | 13 ++----------- banyand/measure/aggregate/percent.go | 14 ++------------ banyand/measure/aggregate/rate.go | 14 ++------------ banyand/measure/aggregate/sum.go | 9 +-------- 8 files changed, 17 insertions(+), 79 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 6adbaa14d..48713baa0 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -26,11 +26,13 @@ import ( ) // Void type contains nothing. It works as a placeholder for type parameters of `Arguments`. -type Void struct{} +// It's implemented as int64, but it won't be used as an int64. +type Void int64 -// Input covers possible types of Function's arguments. It synchronizes with `FieldType` in schema. +// Input covers possible types of Function's arguments. It synchronizes with `FieldType`. +// It also covers Void type. type Input interface { - Void | ~int64 | ~float64 + ~int64 | ~float64 } // Output covers possible types of Function's return value. @@ -38,8 +40,6 @@ type Output interface { ~int64 | ~float64 } -var errFieldValueType = fmt.Errorf("unsupported input value type on this field") - // Arguments represents the argument array, with one argument or two arguments. type Arguments[A, B Input] struct { arg0 []A diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index 7abc686da..682fc758b 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -27,23 +27,11 @@ type Avg[A, B Input, R Output] struct { // Avg uses type parameter A. func (f *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - f.summation += R(arg0) - case float64: - f.summation += R(arg0) - default: - return errFieldValueType - } + f.summation += R(arg0) } for _, arg1 := range arguments.arg1 { - switch arg1 := any(arg1).(type) { - case int64: - f.count += arg1 - default: - return errFieldValueType - } + f.count += int64(arg1) } return nil diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go index 0b2daccd4..139cceaf9 100644 --- a/banyand/measure/aggregate/count.go +++ b/banyand/measure/aggregate/count.go @@ -26,12 +26,7 @@ type Count[A, B Input, R Output] struct { // Count uses none of type parameters. func (f *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - f.count += arg0 - default: - return errFieldValueType - } + f.count += int64(arg0) } return nil } diff --git a/banyand/measure/aggregate/max.go b/banyand/measure/aggregate/max.go index 5879435af..f77b38b99 100644 --- a/banyand/measure/aggregate/max.go +++ b/banyand/measure/aggregate/max.go @@ -26,17 +26,8 @@ type Max[A, B Input, R Output] struct { // Max uses type parameter A. func (f *Max[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - if R(arg0) > f.maximum { - f.maximum = R(arg0) - } - case float64: - if R(arg0) > f.maximum { - f.maximum = R(arg0) - } - default: - return errFieldValueType + if R(arg0) > f.maximum { + f.maximum = R(arg0) } } return nil diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index ab4576345..468d42f1f 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -26,17 +26,8 @@ type Min[A, B Input, R Output] struct { // Min uses type parameter A. func (f *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - if R(arg0) < f.minimum { - f.minimum = R(arg0) - } - case float64: - if R(arg0) < f.minimum { - f.minimum = R(arg0) - } - default: - return errFieldValueType + if R(arg0) < f.minimum { + f.minimum = R(arg0) } } return nil diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index 4dd425937..4b000245b 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -27,21 +27,11 @@ type Percent[A, B Input, R Output] struct { // Percent uses none of type parameters. func (f *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - f.total += arg0 - default: - return errFieldValueType - } + f.total += int64(arg0) } for _, arg1 := range arguments.arg1 { - switch arg1 := any(arg1).(type) { - case int64: - f.match += arg1 - default: - return errFieldValueType - } + f.match += int64(arg1) } return nil diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index a0a0dc9a3..9719c4c32 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -27,21 +27,11 @@ type Rate[A, B Input, R Output] struct { // Rate uses none of type parameters. func (f *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - f.denominator += arg0 - default: - return errFieldValueType - } + f.denominator += int64(arg0) } for _, arg1 := range arguments.arg1 { - switch arg1 := any(arg1).(type) { - case int64: - f.numerator += arg1 - default: - return errFieldValueType - } + f.numerator += int64(arg1) } return nil diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index 96e418e95..867f589dd 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -26,14 +26,7 @@ type Sum[A, B Input, R Output] struct { // Sum uses type parameter A. func (f *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { for _, arg0 := range arguments.arg0 { - switch arg0 := any(arg0).(type) { - case int64: - f.summation += R(arg0) - case float64: - f.summation += R(arg0) - default: - return errFieldValueType - } + f.summation += R(arg0) } return nil From ad6416f9b39c5d5ce367796409160538ed60584a Mon Sep 17 00:00:00 2001 From: StLeoX Date: Tue, 10 Sep 2024 22:03:36 +0800 Subject: [PATCH 24/28] add to Function interface: FirstCumulation and SecondCumulation --- banyand/measure/aggregate/aggregate_function.go | 6 ++++++ banyand/measure/aggregate/avg.go | 10 ++++++++++ banyand/measure/aggregate/avg_test.go | 6 +++++- banyand/measure/aggregate/count.go | 10 ++++++++++ banyand/measure/aggregate/max.go | 10 ++++++++++ banyand/measure/aggregate/min.go | 10 ++++++++++ banyand/measure/aggregate/percent.go | 10 ++++++++++ banyand/measure/aggregate/rate.go | 10 ++++++++++ banyand/measure/aggregate/sum.go | 10 ++++++++++ banyand/measure/aggregate/sum_test.go | 4 ++++ 10 files changed, 85 insertions(+), 1 deletion(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 48713baa0..d84a27e06 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -54,6 +54,12 @@ type Function[A, B Input, R Output] interface { // Result gives the result for the aggregation. Result() R + + // FirstCumulation gives the first cumulative state for the aggregation. + FirstCumulation() A + + // SecondCumulation gives the second cumulative state for the aggregation. + SecondCumulation() B } // NewFunction constructs the aggregate function with given kind and parameter types. diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index 682fc758b..f9947d793 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -48,6 +48,16 @@ func (f *Avg[A, B, R]) Result() R { return f.summation / R(f.count) } +// FirstCumulation gives `summation`. +func (f *Avg[A, B, R]) FirstCumulation() A { + return A(f.summation) +} + +// SecondCumulation gives `count`. +func (f *Avg[A, B, R]) SecondCumulation() B { + return B(f.count) +} + // NewAvgArguments constructs arguments. func NewAvgArguments[A Input](a []A, b []int64) Arguments[A, int64] { return Arguments[A, int64]{ diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index 34801eb12..d892f0429 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -32,11 +32,13 @@ func TestAvg(t *testing.T) { // case1: input int64 values avgInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) err = avgInt64.Combine(aggregate.NewAvgArguments[int64]( - []int64{1, 2, 3}, // mock the "summation" column + []int64{1, 3, 3}, // mock the "summation" column []int64{1, 1, 1}, // mock the "count" column )) assert.NoError(t, err) assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int + assert.Equal(t, int64(7), avgInt64.FirstCumulation()) + assert.Equal(t, int64(3), avgInt64.SecondCumulation()) // case2: input float64 elements avgFloat64, _ := aggregate.NewFunction[float64, int64, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) @@ -46,4 +48,6 @@ func TestAvg(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, 7.0/3, avgFloat64.Result()) + assert.Equal(t, 7.0, avgFloat64.FirstCumulation()) + assert.Equal(t, int64(3), avgFloat64.SecondCumulation()) } diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go index 139cceaf9..7423dd72b 100644 --- a/banyand/measure/aggregate/count.go +++ b/banyand/measure/aggregate/count.go @@ -36,6 +36,16 @@ func (f *Count[A, B, R]) Result() R { return R(f.count) } +// FirstCumulation gives `count`. +func (f *Count[A, B, R]) FirstCumulation() A { + return A(f.count) +} + +// SecondCumulation always gives 0. +func (f *Count[A, B, R]) SecondCumulation() B { + return zeroValue[B]() +} + // NewCountArguments constructs arguments. func NewCountArguments(a []int64) Arguments[int64, Void] { return Arguments[int64, Void]{ diff --git a/banyand/measure/aggregate/max.go b/banyand/measure/aggregate/max.go index f77b38b99..716885dcd 100644 --- a/banyand/measure/aggregate/max.go +++ b/banyand/measure/aggregate/max.go @@ -38,6 +38,16 @@ func (f *Max[A, B, R]) Result() R { return f.maximum } +// FirstCumulation gives `maximum`. +func (f *Max[A, B, R]) FirstCumulation() A { + return A(f.maximum) +} + +// SecondCumulation always gives 0. +func (f *Max[A, B, R]) SecondCumulation() B { + return zeroValue[B]() +} + // NewMaxArguments constructs arguments. func NewMaxArguments[A Input](a []A) Arguments[A, Void] { return Arguments[A, Void]{ diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 468d42f1f..704cfebe4 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -38,6 +38,16 @@ func (f *Min[A, B, R]) Result() R { return f.minimum } +// FirstCumulation gives `minimum`. +func (f *Min[A, B, R]) FirstCumulation() A { + return A(f.minimum) +} + +// SecondCumulation always gives 0. +func (f *Min[A, B, R]) SecondCumulation() B { + return zeroValue[B]() +} + // NewMinArguments constructs arguments. func NewMinArguments[A Input](a []A) Arguments[A, Void] { return Arguments[A, Void]{ diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index 4b000245b..a6fc67d7c 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -48,6 +48,16 @@ func (f *Percent[A, B, R]) Result() R { return R(f.match) * 10000 / R(f.total) } +// FirstCumulation gives `total`. +func (f *Percent[A, B, R]) FirstCumulation() A { + return A(f.total) +} + +// SecondCumulation gives `match`. +func (f *Percent[A, B, R]) SecondCumulation() B { + return B(f.match) +} + // NewPercentArguments constructs arguments. func NewPercentArguments(a []int64, b []int64) Arguments[int64, int64] { return Arguments[int64, int64]{ diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index 9719c4c32..5e7a7ae68 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -47,6 +47,16 @@ func (f *Rate[A, B, R]) Result() R { return R(f.numerator) * 10000 / R(f.denominator) } +// FirstCumulation gives `denominator`. +func (f *Rate[A, B, R]) FirstCumulation() A { + return A(f.denominator) +} + +// SecondCumulation gives `numerator`. +func (f *Rate[A, B, R]) SecondCumulation() B { + return B(f.numerator) +} + // NewRateArguments constructs arguments. func NewRateArguments(a []int64, b []int64) Arguments[int64, int64] { return Arguments[int64, int64]{ diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index 867f589dd..364d213dc 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -37,6 +37,16 @@ func (f *Sum[A, B, R]) Result() R { return f.summation } +// FirstCumulation gives `summation`. +func (f *Sum[A, B, R]) FirstCumulation() A { + return A(f.summation) +} + +// SecondCumulation always gives 0. +func (f *Sum[A, B, R]) SecondCumulation() B { + return zeroValue[B]() +} + // NewSumArguments constructs arguments. func NewSumArguments[A Input](a []A) Arguments[A, Void] { return Arguments[A, Void]{ diff --git a/banyand/measure/aggregate/sum_test.go b/banyand/measure/aggregate/sum_test.go index ff47e1443..e34a6fe49 100644 --- a/banyand/measure/aggregate/sum_test.go +++ b/banyand/measure/aggregate/sum_test.go @@ -36,6 +36,8 @@ func TestSum(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, int64(6), sumInt64.Result()) + assert.Equal(t, int64(6), sumInt64.FirstCumulation()) + assert.Equal(t, aggregate.Void(0), sumInt64.SecondCumulation()) // not int64(0) // case2: input float64 values sumFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) @@ -44,4 +46,6 @@ func TestSum(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, 6.0, sumFloat64.Result()) + assert.Equal(t, 6.0, sumFloat64.FirstCumulation()) + assert.Equal(t, aggregate.Void(0), sumFloat64.SecondCumulation()) // not 0.0 } From 07b537d784a767db8999f45d61e2f65516a46974 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 12 Sep 2024 14:37:41 +0800 Subject: [PATCH 25/28] Revert "add to Function interface: FirstCumulation and SecondCumulation" This reverts commit ad6416f9b39c5d5ce367796409160538ed60584a. --- banyand/measure/aggregate/aggregate_function.go | 6 ------ banyand/measure/aggregate/avg.go | 10 ---------- banyand/measure/aggregate/avg_test.go | 6 +----- banyand/measure/aggregate/count.go | 10 ---------- banyand/measure/aggregate/max.go | 10 ---------- banyand/measure/aggregate/min.go | 10 ---------- banyand/measure/aggregate/percent.go | 10 ---------- banyand/measure/aggregate/rate.go | 10 ---------- banyand/measure/aggregate/sum.go | 10 ---------- banyand/measure/aggregate/sum_test.go | 4 ---- 10 files changed, 1 insertion(+), 85 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index d84a27e06..48713baa0 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -54,12 +54,6 @@ type Function[A, B Input, R Output] interface { // Result gives the result for the aggregation. Result() R - - // FirstCumulation gives the first cumulative state for the aggregation. - FirstCumulation() A - - // SecondCumulation gives the second cumulative state for the aggregation. - SecondCumulation() B } // NewFunction constructs the aggregate function with given kind and parameter types. diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index f9947d793..682fc758b 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -48,16 +48,6 @@ func (f *Avg[A, B, R]) Result() R { return f.summation / R(f.count) } -// FirstCumulation gives `summation`. -func (f *Avg[A, B, R]) FirstCumulation() A { - return A(f.summation) -} - -// SecondCumulation gives `count`. -func (f *Avg[A, B, R]) SecondCumulation() B { - return B(f.count) -} - // NewAvgArguments constructs arguments. func NewAvgArguments[A Input](a []A, b []int64) Arguments[A, int64] { return Arguments[A, int64]{ diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index d892f0429..34801eb12 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -32,13 +32,11 @@ func TestAvg(t *testing.T) { // case1: input int64 values avgInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) err = avgInt64.Combine(aggregate.NewAvgArguments[int64]( - []int64{1, 3, 3}, // mock the "summation" column + []int64{1, 2, 3}, // mock the "summation" column []int64{1, 1, 1}, // mock the "count" column )) assert.NoError(t, err) assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int - assert.Equal(t, int64(7), avgInt64.FirstCumulation()) - assert.Equal(t, int64(3), avgInt64.SecondCumulation()) // case2: input float64 elements avgFloat64, _ := aggregate.NewFunction[float64, int64, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) @@ -48,6 +46,4 @@ func TestAvg(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, 7.0/3, avgFloat64.Result()) - assert.Equal(t, 7.0, avgFloat64.FirstCumulation()) - assert.Equal(t, int64(3), avgFloat64.SecondCumulation()) } diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go index 7423dd72b..139cceaf9 100644 --- a/banyand/measure/aggregate/count.go +++ b/banyand/measure/aggregate/count.go @@ -36,16 +36,6 @@ func (f *Count[A, B, R]) Result() R { return R(f.count) } -// FirstCumulation gives `count`. -func (f *Count[A, B, R]) FirstCumulation() A { - return A(f.count) -} - -// SecondCumulation always gives 0. -func (f *Count[A, B, R]) SecondCumulation() B { - return zeroValue[B]() -} - // NewCountArguments constructs arguments. func NewCountArguments(a []int64) Arguments[int64, Void] { return Arguments[int64, Void]{ diff --git a/banyand/measure/aggregate/max.go b/banyand/measure/aggregate/max.go index 716885dcd..f77b38b99 100644 --- a/banyand/measure/aggregate/max.go +++ b/banyand/measure/aggregate/max.go @@ -38,16 +38,6 @@ func (f *Max[A, B, R]) Result() R { return f.maximum } -// FirstCumulation gives `maximum`. -func (f *Max[A, B, R]) FirstCumulation() A { - return A(f.maximum) -} - -// SecondCumulation always gives 0. -func (f *Max[A, B, R]) SecondCumulation() B { - return zeroValue[B]() -} - // NewMaxArguments constructs arguments. func NewMaxArguments[A Input](a []A) Arguments[A, Void] { return Arguments[A, Void]{ diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 704cfebe4..468d42f1f 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -38,16 +38,6 @@ func (f *Min[A, B, R]) Result() R { return f.minimum } -// FirstCumulation gives `minimum`. -func (f *Min[A, B, R]) FirstCumulation() A { - return A(f.minimum) -} - -// SecondCumulation always gives 0. -func (f *Min[A, B, R]) SecondCumulation() B { - return zeroValue[B]() -} - // NewMinArguments constructs arguments. func NewMinArguments[A Input](a []A) Arguments[A, Void] { return Arguments[A, Void]{ diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index a6fc67d7c..4b000245b 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -48,16 +48,6 @@ func (f *Percent[A, B, R]) Result() R { return R(f.match) * 10000 / R(f.total) } -// FirstCumulation gives `total`. -func (f *Percent[A, B, R]) FirstCumulation() A { - return A(f.total) -} - -// SecondCumulation gives `match`. -func (f *Percent[A, B, R]) SecondCumulation() B { - return B(f.match) -} - // NewPercentArguments constructs arguments. func NewPercentArguments(a []int64, b []int64) Arguments[int64, int64] { return Arguments[int64, int64]{ diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index 5e7a7ae68..9719c4c32 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -47,16 +47,6 @@ func (f *Rate[A, B, R]) Result() R { return R(f.numerator) * 10000 / R(f.denominator) } -// FirstCumulation gives `denominator`. -func (f *Rate[A, B, R]) FirstCumulation() A { - return A(f.denominator) -} - -// SecondCumulation gives `numerator`. -func (f *Rate[A, B, R]) SecondCumulation() B { - return B(f.numerator) -} - // NewRateArguments constructs arguments. func NewRateArguments(a []int64, b []int64) Arguments[int64, int64] { return Arguments[int64, int64]{ diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index 364d213dc..867f589dd 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -37,16 +37,6 @@ func (f *Sum[A, B, R]) Result() R { return f.summation } -// FirstCumulation gives `summation`. -func (f *Sum[A, B, R]) FirstCumulation() A { - return A(f.summation) -} - -// SecondCumulation always gives 0. -func (f *Sum[A, B, R]) SecondCumulation() B { - return zeroValue[B]() -} - // NewSumArguments constructs arguments. func NewSumArguments[A Input](a []A) Arguments[A, Void] { return Arguments[A, Void]{ diff --git a/banyand/measure/aggregate/sum_test.go b/banyand/measure/aggregate/sum_test.go index e34a6fe49..ff47e1443 100644 --- a/banyand/measure/aggregate/sum_test.go +++ b/banyand/measure/aggregate/sum_test.go @@ -36,8 +36,6 @@ func TestSum(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, int64(6), sumInt64.Result()) - assert.Equal(t, int64(6), sumInt64.FirstCumulation()) - assert.Equal(t, aggregate.Void(0), sumInt64.SecondCumulation()) // not int64(0) // case2: input float64 values sumFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) @@ -46,6 +44,4 @@ func TestSum(t *testing.T) { )) assert.NoError(t, err) assert.Equal(t, 6.0, sumFloat64.Result()) - assert.Equal(t, 6.0, sumFloat64.FirstCumulation()) - assert.Equal(t, aggregate.Void(0), sumFloat64.SecondCumulation()) // not 0.0 } From 93ca0159f3e532fe3ce9e80a85c416fe54eed68a Mon Sep 17 00:00:00 2001 From: StLeoX Date: Thu, 12 Sep 2024 14:57:28 +0800 Subject: [PATCH 26/28] change `Result() R` to `Result() (A, B, R)` --- banyand/measure/aggregate/aggregate_function.go | 5 +++-- banyand/measure/aggregate/avg.go | 8 +++++--- banyand/measure/aggregate/avg_test.go | 13 ++++++++++--- banyand/measure/aggregate/count.go | 4 ++-- banyand/measure/aggregate/count_test.go | 3 ++- banyand/measure/aggregate/max.go | 4 ++-- banyand/measure/aggregate/max_test.go | 6 ++++-- banyand/measure/aggregate/min.go | 4 ++-- banyand/measure/aggregate/min_test.go | 6 ++++-- banyand/measure/aggregate/percent.go | 8 +++++--- banyand/measure/aggregate/percent_test.go | 7 ++++--- banyand/measure/aggregate/rate.go | 8 +++++--- banyand/measure/aggregate/rate_test.go | 3 ++- banyand/measure/aggregate/sum.go | 4 ++-- banyand/measure/aggregate/sum_test.go | 6 ++++-- 15 files changed, 56 insertions(+), 33 deletions(-) diff --git a/banyand/measure/aggregate/aggregate_function.go b/banyand/measure/aggregate/aggregate_function.go index 48713baa0..6e935825b 100644 --- a/banyand/measure/aggregate/aggregate_function.go +++ b/banyand/measure/aggregate/aggregate_function.go @@ -52,8 +52,9 @@ type Function[A, B Input, R Output] interface { // It uses a two-dimensional array to represent the argument array. Combine(arguments Arguments[A, B]) error - // Result gives the result for the aggregation. - Result() R + // Result gives the result for the aggregation. R is the aggregating result, + // A is the first aggregating state, and B is the second aggregating state. + Result() (A, B, R) } // NewFunction constructs the aggregate function with given kind and parameter types. diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index 682fc758b..af4a9aa0b 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -38,14 +38,16 @@ func (f *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Avg[A, B, R]) Result() R { +func (f *Avg[A, B, R]) Result() (A, B, R) { + var average R // In unusual situations it returns the zero value. if f.count == 0 { - return zeroValue[R]() + average = zeroValue[R]() } // According to the semantics of GoLang, the division of one int by another int // returns an int, instead of f float. - return f.summation / R(f.count) + average = f.summation / R(f.count) + return A(f.summation), B(f.count), average } // NewAvgArguments constructs arguments. diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index 34801eb12..0fee5c379 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -32,11 +32,14 @@ func TestAvg(t *testing.T) { // case1: input int64 values avgInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) err = avgInt64.Combine(aggregate.NewAvgArguments[int64]( - []int64{1, 2, 3}, // mock the "summation" column + []int64{1, 3, 3}, // mock the "summation" column []int64{1, 1, 1}, // mock the "count" column )) assert.NoError(t, err) - assert.Equal(t, int64(2), avgInt64.Result()) // note that 7/3 becomes 2 as int + a1, b1, r1 := avgInt64.Result() + assert.Equal(t, int64(7), a1) + assert.Equal(t, int64(3), b1) + assert.Equal(t, int64(2), r1) // note that 7/3 becomes 2 as int // case2: input float64 elements avgFloat64, _ := aggregate.NewFunction[float64, int64, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_AVG) @@ -45,5 +48,9 @@ func TestAvg(t *testing.T) { []int64{1, 1, 1}, // mock the "count" column )) assert.NoError(t, err) - assert.Equal(t, 7.0/3, avgFloat64.Result()) + a2, b2, r2 := avgFloat64.Result() + assert.Equal(t, 7.0, a2) + assert.Equal(t, int64(3), b2) + assert.Equal(t, 7.0/3, r2) + } diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go index 139cceaf9..a55cae6fb 100644 --- a/banyand/measure/aggregate/count.go +++ b/banyand/measure/aggregate/count.go @@ -32,8 +32,8 @@ func (f *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Count[A, B, R]) Result() R { - return R(f.count) +func (f *Count[A, B, R]) Result() (A, B, R) { + return A(f.count), zeroValue[B](), R(f.count) } // NewCountArguments constructs arguments. diff --git a/banyand/measure/aggregate/count_test.go b/banyand/measure/aggregate/count_test.go index db6a0b30c..13bf9b0ba 100644 --- a/banyand/measure/aggregate/count_test.go +++ b/banyand/measure/aggregate/count_test.go @@ -35,5 +35,6 @@ func TestCount(t *testing.T) { []int64{1, 2, 3}, // mock the "count" column )) assert.NoError(t, err) - assert.Equal(t, int64(6), countInt64.Result()) + _, _, r1 := countInt64.Result() + assert.Equal(t, int64(6), r1) } diff --git a/banyand/measure/aggregate/max.go b/banyand/measure/aggregate/max.go index f77b38b99..995fb888f 100644 --- a/banyand/measure/aggregate/max.go +++ b/banyand/measure/aggregate/max.go @@ -34,8 +34,8 @@ func (f *Max[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Max[A, B, R]) Result() R { - return f.maximum +func (f *Max[A, B, R]) Result() (A, B, R) { + return A(f.maximum), zeroValue[B](), f.maximum } // NewMaxArguments constructs arguments. diff --git a/banyand/measure/aggregate/max_test.go b/banyand/measure/aggregate/max_test.go index 361877174..7a99cf215 100644 --- a/banyand/measure/aggregate/max_test.go +++ b/banyand/measure/aggregate/max_test.go @@ -35,7 +35,8 @@ func TestMax(t *testing.T) { []int64{1, 2, 3}, // mock the "maximum" column )) assert.NoError(t, err) - assert.Equal(t, int64(3), maxInt64.Result()) + _, _, r1 := maxInt64.Result() + assert.Equal(t, int64(3), r1) // case2: input float64 values maxFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MAX) @@ -43,5 +44,6 @@ func TestMax(t *testing.T) { []float64{1.0, 2.0, 3.0}, // mock the "maximum" column )) assert.NoError(t, err) - assert.Equal(t, 3.0, maxFloat64.Result()) + _, _, r2 := maxFloat64.Result() + assert.Equal(t, 3.0, r2) } diff --git a/banyand/measure/aggregate/min.go b/banyand/measure/aggregate/min.go index 468d42f1f..27b0f6660 100644 --- a/banyand/measure/aggregate/min.go +++ b/banyand/measure/aggregate/min.go @@ -34,8 +34,8 @@ func (f *Min[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Min[A, B, R]) Result() R { - return f.minimum +func (f *Min[A, B, R]) Result() (A, B, R) { + return A(f.minimum), zeroValue[B](), f.minimum } // NewMinArguments constructs arguments. diff --git a/banyand/measure/aggregate/min_test.go b/banyand/measure/aggregate/min_test.go index fa6fd9499..e642e5155 100644 --- a/banyand/measure/aggregate/min_test.go +++ b/banyand/measure/aggregate/min_test.go @@ -35,7 +35,8 @@ func TestMin(t *testing.T) { []int64{1, 2, 3}, // mock the "minimum" column )) assert.NoError(t, err) - assert.Equal(t, int64(1), minInt64.Result()) + _, _, r1 := minInt64.Result() + assert.Equal(t, int64(1), r1) // case2: input float64 values minFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_MIN) @@ -43,5 +44,6 @@ func TestMin(t *testing.T) { []float64{1.0, 2.0, 3.0}, // mock the "minimum" column )) assert.NoError(t, err) - assert.Equal(t, 1.0, minFloat64.Result()) + _, _, r2 := minFloat64.Result() + assert.Equal(t, 1.0, r2) } diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index 4b000245b..9e24c770b 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -38,14 +38,16 @@ func (f *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Percent[A, B, R]) Result() R { +func (f *Percent[A, B, R]) Result() (A, B, R) { + var percent R // In unusual situations it returns the zero value. if f.total == 0 { - return zeroValue[R]() + percent = zeroValue[R]() } // Factory 10000 is used to improve accuracy. This factory is same as OAP. // For example, "10 percent" will return 1000. - return R(f.match) * 10000 / R(f.total) + percent = R(f.match) * 10000 / R(f.total) + return A(f.total), B(f.match), percent } // NewPercentArguments constructs arguments. diff --git a/banyand/measure/aggregate/percent_test.go b/banyand/measure/aggregate/percent_test.go index eb24a9b67..0dbb2d422 100644 --- a/banyand/measure/aggregate/percent_test.go +++ b/banyand/measure/aggregate/percent_test.go @@ -30,11 +30,12 @@ func TestPercent(t *testing.T) { var err error // case1: input int64 values - PercentInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT) - err = PercentInt64.Combine(aggregate.NewPercentArguments( + percentInt64, _ := aggregate.NewFunction[int64, int64, int64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_PERCENT) + err = percentInt64.Combine(aggregate.NewPercentArguments( []int64{10, 100, 1000}, // mock the "total" column []int64{1, 10, 100}, // mock the "match" column )) assert.NoError(t, err) - assert.Equal(t, int64(1000), PercentInt64.Result()) + _, _, r1 := percentInt64.Result() + assert.Equal(t, int64(1000), r1) } diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index 9719c4c32..d37ece9e4 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -38,13 +38,15 @@ func (f *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Rate[A, B, R]) Result() R { +func (f *Rate[A, B, R]) Result() (A, B, R) { + var rate R // In unusual situations it returns the zero value. if f.denominator == 0 { - return zeroValue[R]() + rate = zeroValue[R]() } // Factory 10000 is used to improve accuracy. This factory is same as OAP. - return R(f.numerator) * 10000 / R(f.denominator) + rate = R(f.numerator) * 10000 / R(f.denominator) + return A(f.denominator), B(f.numerator), rate } // NewRateArguments constructs arguments. diff --git a/banyand/measure/aggregate/rate_test.go b/banyand/measure/aggregate/rate_test.go index e03989f88..50b3f6ea3 100644 --- a/banyand/measure/aggregate/rate_test.go +++ b/banyand/measure/aggregate/rate_test.go @@ -36,5 +36,6 @@ func TestRate(t *testing.T) { []int64{1, 10, 100}, // mock the "numerator" column )) assert.NoError(t, err) - assert.Equal(t, int64(1000), rateInt64.Result()) + _, _, r1 := rateInt64.Result() + assert.Equal(t, int64(1000), r1) } diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index 867f589dd..2f3c474df 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -33,8 +33,8 @@ func (f *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { } // Result gives the result for the aggregation. -func (f *Sum[A, B, R]) Result() R { - return f.summation +func (f *Sum[A, B, R]) Result() (A, B, R) { + return A(f.summation), zeroValue[B](), f.summation } // NewSumArguments constructs arguments. diff --git a/banyand/measure/aggregate/sum_test.go b/banyand/measure/aggregate/sum_test.go index ff47e1443..942a21262 100644 --- a/banyand/measure/aggregate/sum_test.go +++ b/banyand/measure/aggregate/sum_test.go @@ -35,7 +35,8 @@ func TestSum(t *testing.T) { []int64{1, 2, 3}, // mock the "summation" column )) assert.NoError(t, err) - assert.Equal(t, int64(6), sumInt64.Result()) + _, _, r1 := sumInt64.Result() + assert.Equal(t, int64(6), r1) // case2: input float64 values sumFloat64, _ := aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) @@ -43,5 +44,6 @@ func TestSum(t *testing.T) { []float64{1.0, 2.0, 3.0}, // mock the "summation" column )) assert.NoError(t, err) - assert.Equal(t, 6.0, sumFloat64.Result()) + _, _, r2 := sumFloat64.Result() + assert.Equal(t, 6.0, r2) } From 89f07d5f149b761b4d7f19bf957e0d1617408e20 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Wed, 18 Sep 2024 23:11:20 +0800 Subject: [PATCH 27/28] introduce step-4 loop unrolling for sum-like aggregate functions --- banyand/measure/aggregate/avg.go | 24 ++++++++++++++++++++---- banyand/measure/aggregate/count.go | 12 ++++++++++-- banyand/measure/aggregate/percent.go | 24 ++++++++++++++++++++---- banyand/measure/aggregate/rate.go | 24 ++++++++++++++++++++---- banyand/measure/aggregate/sum.go | 12 ++++++++++-- banyand/measure/aggregate/sum_test.go | 19 +++++++++++++++++++ 6 files changed, 99 insertions(+), 16 deletions(-) diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index af4a9aa0b..772886c8a 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -26,12 +26,28 @@ type Avg[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Avg uses type parameter A. func (f *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { - for _, arg0 := range arguments.arg0 { - f.summation += R(arg0) + i := 0 + n := len(arguments.arg0) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.summation += R(arguments.arg0[i]) + R(arguments.arg0[i+1]) + + R(arguments.arg0[i+2]) + R(arguments.arg0[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.summation += R(arguments.arg0[i]) } - for _, arg1 := range arguments.arg1 { - f.count += int64(arg1) + i = 0 + n = len(arguments.arg1) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.count += int64(arguments.arg1[i]) + int64(arguments.arg1[i+1]) + + int64(arguments.arg1[i+2]) + int64(arguments.arg1[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.count += int64(arguments.arg1[i]) } return nil diff --git a/banyand/measure/aggregate/count.go b/banyand/measure/aggregate/count.go index a55cae6fb..ec2390f4c 100644 --- a/banyand/measure/aggregate/count.go +++ b/banyand/measure/aggregate/count.go @@ -25,8 +25,16 @@ type Count[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Count uses none of type parameters. func (f *Count[A, B, R]) Combine(arguments Arguments[A, B]) error { - for _, arg0 := range arguments.arg0 { - f.count += int64(arg0) + i := 0 + n := len(arguments.arg0) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.count += int64(arguments.arg0[i]) + int64(arguments.arg0[i+1]) + + int64(arguments.arg0[i+2]) + int64(arguments.arg0[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.count += int64(arguments.arg0[i]) } return nil } diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index 9e24c770b..0f31b9d29 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -26,12 +26,28 @@ type Percent[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Percent uses none of type parameters. func (f *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { - for _, arg0 := range arguments.arg0 { - f.total += int64(arg0) + i := 0 + n := len(arguments.arg0) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.total += int64(arguments.arg0[i]) + int64(arguments.arg0[i+1]) + + int64(arguments.arg0[i+2]) + int64(arguments.arg0[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.total += int64(arguments.arg0[i]) } - for _, arg1 := range arguments.arg1 { - f.match += int64(arg1) + i = 0 + n = len(arguments.arg1) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.match += int64(arguments.arg1[i]) + int64(arguments.arg1[i+1]) + + int64(arguments.arg1[i+2]) + int64(arguments.arg1[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.match += int64(arguments.arg1[i]) } return nil diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index d37ece9e4..1a60c000d 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -26,12 +26,28 @@ type Rate[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Rate uses none of type parameters. func (f *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { - for _, arg0 := range arguments.arg0 { - f.denominator += int64(arg0) + i := 0 + n := len(arguments.arg0) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.denominator += int64(arguments.arg0[i]) + int64(arguments.arg0[i+1]) + + int64(arguments.arg0[i+2]) + int64(arguments.arg0[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.denominator += int64(arguments.arg0[i]) } - for _, arg1 := range arguments.arg1 { - f.numerator += int64(arg1) + i = 0 + n = len(arguments.arg1) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.numerator += int64(arguments.arg1[i]) + int64(arguments.arg1[i+1]) + + int64(arguments.arg1[i+2]) + int64(arguments.arg1[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.numerator += int64(arguments.arg1[i]) } return nil diff --git a/banyand/measure/aggregate/sum.go b/banyand/measure/aggregate/sum.go index 2f3c474df..cdac56c5c 100644 --- a/banyand/measure/aggregate/sum.go +++ b/banyand/measure/aggregate/sum.go @@ -25,8 +25,16 @@ type Sum[A, B Input, R Output] struct { // Combine takes elements to do the aggregation. // Sum uses type parameter A. func (f *Sum[A, B, R]) Combine(arguments Arguments[A, B]) error { - for _, arg0 := range arguments.arg0 { - f.summation += R(arg0) + i := 0 + n := len(arguments.arg0) + // step-4 aggregate + for ; i <= n-4; i += 4 { + f.summation += R(arguments.arg0[i]) + R(arguments.arg0[i+1]) + + R(arguments.arg0[i+2]) + R(arguments.arg0[i+3]) + } + // tail aggregate + for ; i < n; i++ { + f.summation += R(arguments.arg0[i]) } return nil diff --git a/banyand/measure/aggregate/sum_test.go b/banyand/measure/aggregate/sum_test.go index 942a21262..e30713ec6 100644 --- a/banyand/measure/aggregate/sum_test.go +++ b/banyand/measure/aggregate/sum_test.go @@ -46,4 +46,23 @@ func TestSum(t *testing.T) { assert.NoError(t, err) _, _, r2 := sumFloat64.Result() assert.Equal(t, 6.0, r2) + + // case3: 7 values inputted + sumFloat64, _ = aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) + err = sumFloat64.Combine(aggregate.NewSumArguments[float64]( + []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}, // mock the "summation" column + )) + assert.NoError(t, err) + _, _, r3 := sumFloat64.Result() + assert.Equal(t, 28.0, r3) + + // case4: 4 values inputted + sumFloat64, _ = aggregate.NewFunction[float64, aggregate.Void, float64](modelv1.MeasureAggregate_MEASURE_AGGREGATE_SUM) + err = sumFloat64.Combine(aggregate.NewSumArguments[float64]( + []float64{1.0, 2.0, 3.0, 4.0}, // mock the "summation" column + )) + assert.NoError(t, err) + _, _, r4 := sumFloat64.Result() + assert.Equal(t, 10.0, r4) + } From 645c4f35af49eb1834ceb6a894e85e147af007a0 Mon Sep 17 00:00:00 2001 From: StLeoX Date: Sat, 12 Oct 2024 14:43:02 +0800 Subject: [PATCH 28/28] fix lint --- banyand/measure/aggregate/avg.go | 10 ++++------ banyand/measure/aggregate/avg_test.go | 1 - banyand/measure/aggregate/percent.go | 10 ++++------ banyand/measure/aggregate/rate.go | 8 +++----- banyand/measure/aggregate/sum_test.go | 1 - 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/banyand/measure/aggregate/avg.go b/banyand/measure/aggregate/avg.go index 772886c8a..5f7043ab0 100644 --- a/banyand/measure/aggregate/avg.go +++ b/banyand/measure/aggregate/avg.go @@ -56,13 +56,11 @@ func (f *Avg[A, B, R]) Combine(arguments Arguments[A, B]) error { // Result gives the result for the aggregation. func (f *Avg[A, B, R]) Result() (A, B, R) { var average R - // In unusual situations it returns the zero value. - if f.count == 0 { - average = zeroValue[R]() + if f.count != 0 { + // According to the semantics of GoLang, the division of one int by another int + // returns an int, instead of f float. + average = f.summation / R(f.count) } - // According to the semantics of GoLang, the division of one int by another int - // returns an int, instead of f float. - average = f.summation / R(f.count) return A(f.summation), B(f.count), average } diff --git a/banyand/measure/aggregate/avg_test.go b/banyand/measure/aggregate/avg_test.go index 0fee5c379..cc9321986 100644 --- a/banyand/measure/aggregate/avg_test.go +++ b/banyand/measure/aggregate/avg_test.go @@ -52,5 +52,4 @@ func TestAvg(t *testing.T) { assert.Equal(t, 7.0, a2) assert.Equal(t, int64(3), b2) assert.Equal(t, 7.0/3, r2) - } diff --git a/banyand/measure/aggregate/percent.go b/banyand/measure/aggregate/percent.go index 0f31b9d29..86475bf71 100644 --- a/banyand/measure/aggregate/percent.go +++ b/banyand/measure/aggregate/percent.go @@ -56,13 +56,11 @@ func (f *Percent[A, B, R]) Combine(arguments Arguments[A, B]) error { // Result gives the result for the aggregation. func (f *Percent[A, B, R]) Result() (A, B, R) { var percent R - // In unusual situations it returns the zero value. - if f.total == 0 { - percent = zeroValue[R]() + if f.total != 0 { + // Factory 10000 is used to improve accuracy. This factory is same as OAP. + // For example, "10 percent" will return 1000. + percent = R(f.match) * 10000 / R(f.total) } - // Factory 10000 is used to improve accuracy. This factory is same as OAP. - // For example, "10 percent" will return 1000. - percent = R(f.match) * 10000 / R(f.total) return A(f.total), B(f.match), percent } diff --git a/banyand/measure/aggregate/rate.go b/banyand/measure/aggregate/rate.go index 1a60c000d..a729f03ee 100644 --- a/banyand/measure/aggregate/rate.go +++ b/banyand/measure/aggregate/rate.go @@ -56,12 +56,10 @@ func (f *Rate[A, B, R]) Combine(arguments Arguments[A, B]) error { // Result gives the result for the aggregation. func (f *Rate[A, B, R]) Result() (A, B, R) { var rate R - // In unusual situations it returns the zero value. - if f.denominator == 0 { - rate = zeroValue[R]() + if f.denominator != 0 { + // Factory 10000 is used to improve accuracy. This factory is same as OAP. + rate = R(f.numerator) * 10000 / R(f.denominator) } - // Factory 10000 is used to improve accuracy. This factory is same as OAP. - rate = R(f.numerator) * 10000 / R(f.denominator) return A(f.denominator), B(f.numerator), rate } diff --git a/banyand/measure/aggregate/sum_test.go b/banyand/measure/aggregate/sum_test.go index e30713ec6..07623a2f4 100644 --- a/banyand/measure/aggregate/sum_test.go +++ b/banyand/measure/aggregate/sum_test.go @@ -64,5 +64,4 @@ func TestSum(t *testing.T) { assert.NoError(t, err) _, _, r4 := sumFloat64.Result() assert.Equal(t, 10.0, r4) - }