From ec18e2bfc3ab8c48ac79cea483487719ee5ae12b Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 20 Nov 2019 13:34:44 +0100 Subject: [PATCH] CloudWatch: Remove HighResolution toggle since it's not being used (#20440) * Remove highres flag since it's not being used * Remove not used code. Init id field correctly * Fix broken tests * Remove GMS related calculations * Rename period field * Add breaking changes to changelog. Also update upgrading docs * Update snapshot * Update docs after feedback * Changes after feedback --- CHANGELOG.md | 4 + docs/sources/installation/upgrading.md | 2 + pkg/tsdb/cloudwatch/cloudwatch_query.go | 2 - pkg/tsdb/cloudwatch/cloudwatch_query_test.go | 6 - .../cloudwatch/metric_data_input_builder.go | 6 - .../metric_data_query_builder_test.go | 10 -- pkg/tsdb/cloudwatch/query_transformer.go | 25 ++-- pkg/tsdb/cloudwatch/query_transformer_test.go | 120 ++++++++---------- pkg/tsdb/cloudwatch/request_parser.go | 26 ++-- pkg/tsdb/cloudwatch/request_parser_test.go | 16 +-- pkg/tsdb/cloudwatch/response_parser.go | 2 +- pkg/tsdb/cloudwatch/types.go | 1 - .../components/QueryEditor.test.tsx | 1 - .../cloudwatch/components/QueryEditor.tsx | 24 ++-- .../__snapshots__/QueryEditor.test.tsx.snap | 31 +---- .../datasource/cloudwatch/dashboards/EBS.json | 9 -- .../cloudwatch/dashboards/Lambda.json | 4 - .../datasource/cloudwatch/dashboards/ec2.json | 11 -- .../datasource/cloudwatch/datasource.ts | 21 ++- .../cloudwatch/partials/query.parameter.html | 10 -- .../cloudwatch/query_parameter_ctrl.ts | 1 - .../cloudwatch/specs/datasource.test.ts | 2 +- .../plugins/datasource/cloudwatch/types.ts | 1 - 23 files changed, 114 insertions(+), 221 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865cbc3f88d..e855e9cfcea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,10 @@ ## Breaking changes * **CloudWatch**: Pre Grafana 6.5.0, the CloudWatch datasource used the GetMetricStatistics API for all queries that did not have an ´id´ and did not have an ´expression´ defined in the query editor. The GetMetricStatistics API has a limit of 400 transactions per second. In this release, all queries use the GetMetricData API. The GetMetricData API has a limit of 50 transactions per second and 100 metrics per transaction. Also the GetMetricData API pricing is different from GetMetricStatistics. While GetMetricStatistics qualified for the CloudWatch API free tier, this is not the case for GetMetricData calls. For more information, please refer to the CloudWatch pricing page (https://aws.amazon.com/cloudwatch/pricing/). Read more about GetMetricData limits in [upgrading to 6.5](https://grafana.com/docs/installation/upgrading/#upgrading-to-v6-5). +* **CloudWatch**: The GetMetricData API does not return metric unit, so unit auto detection in panels is no longer supported. + +* **CloudWatch**: The `HighRes` switch has been removed from the query editor. Read more about this in [upgrading to 6.5](https://grafana.com/docs/installation/upgrading/#upgrading-to-v6-5). + * **CloudWatch**: In previous versions of Grafana, there was partial support for using multi template variables as dimension values. When a multi template variable is being used for dimension values in Grafana 6.5, a [search expression](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-search-expressions.html) will be generated. In the GetMetricData API, expressions are limited to 1024 characters, so it might be the case that this limit is reached when a multi template variable that has a lot of values is being used. Read about the suggested workaround in [upgrading to 6.5](https://grafana.com/docs/installation/upgrading/#upgrading-to-v6-5). # 6.4.4 (2019-11-06) diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index a21b601729e..df2126d9028 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -206,4 +206,6 @@ Plugins that need updating: Pre Grafana 6.5.0, the CloudWatch datasource used the GetMetricStatistics API for all queries that did not have an ´id´ and did not have an ´expression´ defined in the query editor. The GetMetricStatistics API has a limit of 400 transactions per second (TPS). In this release, all queries use the GetMetricData API which has a limit of 50 TPS and 100 metrics per transaction. We expect this transition to be smooth for most of our users, but in case you do face throttling issues we suggest you increase the TPS quota. To do that, please visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home?r#!/services/monitoring/quotas/L-5E141212). For more details around CloudWatch API limits, [see CloudWatch docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_limits.html). +Each request to the GetMetricData API can include 100 queries. This means that each panel in Grafana will only issue one GetMetricData request, regardless of the number of query rows that are present in the panel. Consequently as it is no longer possible to set `HighRes` on a per query level anymore, this switch is now removed from the query editor. High resolution can still be achieved by choosing a smaller minimum period in the query editor. + The handling of multi template variables in dimension values has been changed in Grafana 6.5. When a multi template variable is being used, Grafana will generate a search expression. In the GetMetricData API, expressions are limited to 1024 characters, so it might be the case that this limit is reached when a multi template variable that has a lot of values is being used. If this is the case, we suggest you start using `*` wildcard as dimension value instead of a multi template variable. diff --git a/pkg/tsdb/cloudwatch/cloudwatch_query.go b/pkg/tsdb/cloudwatch/cloudwatch_query.go index fdb62373fbe..1386626e5b4 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch_query.go +++ b/pkg/tsdb/cloudwatch/cloudwatch_query.go @@ -16,8 +16,6 @@ type cloudWatchQuery struct { Dimensions map[string][]string Period int Alias string - Identifier string - HighResolution bool MatchExact bool UsedExpression string RequestExceededMaxLimit bool diff --git a/pkg/tsdb/cloudwatch/cloudwatch_query_test.go b/pkg/tsdb/cloudwatch/cloudwatch_query_test.go index f061d1c47c9..792c0bfd6c8 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch_query_test.go +++ b/pkg/tsdb/cloudwatch/cloudwatch_query_test.go @@ -16,7 +16,6 @@ func TestCloudWatchQuery(t *testing.T) { Stats: "Average", Period: 300, Id: "id1", - Identifier: "id1", } Convey("it is a search expression", func() { @@ -36,7 +35,6 @@ func TestCloudWatchQuery(t *testing.T) { Stats: "Average", Period: 300, Id: "id1", - Identifier: "id1", MatchExact: true, Dimensions: map[string][]string{ "InstanceId": {"i-12345678"}, @@ -60,7 +58,6 @@ func TestCloudWatchQuery(t *testing.T) { Stats: "Average", Period: 300, Id: "id1", - Identifier: "id1", Dimensions: map[string][]string{ "InstanceId": {"i-12345678", "i-34562312"}, }, @@ -83,7 +80,6 @@ func TestCloudWatchQuery(t *testing.T) { Stats: "Average", Period: 300, Id: "id1", - Identifier: "id1", Dimensions: map[string][]string{ "InstanceId": {"i-12345678", "*"}, "InstanceType": {"abc", "def"}, @@ -108,7 +104,6 @@ func TestCloudWatchQuery(t *testing.T) { Period: 300, Id: "id1", MatchExact: false, - Identifier: "id1", Dimensions: make(map[string][]string), } Convey("and match exact is false", func() { @@ -152,7 +147,6 @@ func TestCloudWatchQuery(t *testing.T) { Stats: "Average", Period: 300, Id: "id1", - Identifier: "id1", MatchExact: false, Dimensions: map[string][]string{ "InstanceId": {"i-12345678"}, diff --git a/pkg/tsdb/cloudwatch/metric_data_input_builder.go b/pkg/tsdb/cloudwatch/metric_data_input_builder.go index cb8b5083f0c..c1df09f6200 100644 --- a/pkg/tsdb/cloudwatch/metric_data_input_builder.go +++ b/pkg/tsdb/cloudwatch/metric_data_input_builder.go @@ -1,7 +1,6 @@ package cloudwatch import ( - "errors" "fmt" "github.com/aws/aws-sdk-go/aws" @@ -30,11 +29,6 @@ func (e *CloudWatchExecutor) buildMetricDataInput(queryContext *tsdb.TsdbQuery, ScanBy: aws.String("TimestampAscending"), } for _, query := range queries { - // 1 minutes resolution metrics is stored for 15 days, 15 * 24 * 60 = 21600 - if query.HighResolution && (((endTime.Unix() - startTime.Unix()) / int64(query.Period)) > 21600) { - return nil, &queryError{errors.New("too long query period"), query.RefId} - } - metricDataQuery, err := e.buildMetricDataQuery(query) if err != nil { return nil, &queryError{err, query.RefId} diff --git a/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go b/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go index 54619ad0df9..e57d4769547 100644 --- a/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go +++ b/pkg/tsdb/cloudwatch/metric_data_query_builder_test.go @@ -19,7 +19,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "LoadBalancer": {"lb1", "lb2", "lb3"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -38,7 +37,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "InstanceId": {"i-123", "i-456", "i-789"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -55,7 +53,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "LoadBalancer": {"*"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -72,7 +69,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "LoadBalancer": {"*"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -90,7 +86,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "InstanceId": {"i-123", "*", "i-789"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -110,7 +105,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "LoadBalancer": {"lb1", "lb2", "lb3"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -128,7 +122,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "InstanceId": {"i-123", "i-456", "i-789"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -145,7 +138,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "LoadBalancer": {"*"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -163,7 +155,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "InstanceId": {"i-123", "*", "i-789"}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: matchExact, } @@ -188,7 +179,6 @@ func TestMetricDataQueryBuilder(t *testing.T) { "lb6": {`l\\(b5"`}, }, Period: 300, - Identifier: "id1", Expression: "", MatchExact: true, } diff --git a/pkg/tsdb/cloudwatch/query_transformer.go b/pkg/tsdb/cloudwatch/query_transformer.go index 2ba5c729447..f4e215a504a 100644 --- a/pkg/tsdb/cloudwatch/query_transformer.go +++ b/pkg/tsdb/cloudwatch/query_transformer.go @@ -26,19 +26,18 @@ func (e *CloudWatchExecutor) transformRequestQueriesToCloudWatchQueries(requestQ } query := &cloudWatchQuery{ - Id: id, - RefId: requestQuery.RefId, - Region: requestQuery.Region, - Namespace: requestQuery.Namespace, - MetricName: requestQuery.MetricName, - Dimensions: requestQuery.Dimensions, - Stats: *stat, - Period: requestQuery.Period, - Alias: requestQuery.Alias, - Expression: requestQuery.Expression, - ReturnData: requestQuery.ReturnData, - HighResolution: requestQuery.HighResolution, - MatchExact: requestQuery.MatchExact, + Id: id, + RefId: requestQuery.RefId, + Region: requestQuery.Region, + Namespace: requestQuery.Namespace, + MetricName: requestQuery.MetricName, + Dimensions: requestQuery.Dimensions, + Stats: *stat, + Period: requestQuery.Period, + Alias: requestQuery.Alias, + Expression: requestQuery.Expression, + ReturnData: requestQuery.ReturnData, + MatchExact: requestQuery.MatchExact, } if _, ok := cloudwatchQueries[id]; ok { diff --git a/pkg/tsdb/cloudwatch/query_transformer_test.go b/pkg/tsdb/cloudwatch/query_transformer_test.go index cef9b6366d4..5e05e59b001 100644 --- a/pkg/tsdb/cloudwatch/query_transformer_test.go +++ b/pkg/tsdb/cloudwatch/query_transformer_test.go @@ -15,14 +15,13 @@ func TestQueryTransformer(t *testing.T) { Convey("one cloudwatchQuery is generated when its request query has one stat", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average"}), - Period: 600, - Id: "", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average"}), + Period: 600, + Id: "", }, } @@ -34,14 +33,13 @@ func TestQueryTransformer(t *testing.T) { Convey("two cloudwatchQuery is generated when there's two stats", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average", "Sum"}), - Period: 600, - Id: "", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average", "Sum"}), + Period: 600, + Id: "", }, } @@ -53,14 +51,13 @@ func TestQueryTransformer(t *testing.T) { Convey("that id will be used in the cloudwatch query", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average"}), - Period: 600, - Id: "myid", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average"}), + Period: 600, + Id: "myid", }, } @@ -75,14 +72,13 @@ func TestQueryTransformer(t *testing.T) { Convey("id will be generated based on ref id if query only has one stat", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average"}), - Period: 600, - Id: "", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average"}), + Period: 600, + Id: "", }, } @@ -95,14 +91,13 @@ func TestQueryTransformer(t *testing.T) { Convey("id will be generated based on ref and stat name if query has two stats", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average", "Sum"}), - Period: 600, - Id: "", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average", "Sum"}), + Period: 600, + Id: "", }, } @@ -117,14 +112,13 @@ func TestQueryTransformer(t *testing.T) { Convey("dot should be removed when query has more than one stat and one of them is a percentile", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average", "p46.32"}), - Period: 600, - Id: "", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average", "p46.32"}), + Period: 600, + Id: "", }, } @@ -137,24 +131,22 @@ func TestQueryTransformer(t *testing.T) { Convey("should return an error if two queries have the same id", func() { requestQueries := []*requestQuery{ { - RefId: "D", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average", "p46.32"}), - Period: 600, - Id: "myId", - HighResolution: false, + RefId: "D", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average", "p46.32"}), + Period: 600, + Id: "myId", }, { - RefId: "E", - Region: "us-east-1", - Namespace: "ec2", - MetricName: "CPUUtilization", - Statistics: aws.StringSlice([]string{"Average", "p46.32"}), - Period: 600, - Id: "myId", - HighResolution: false, + RefId: "E", + Region: "us-east-1", + Namespace: "ec2", + MetricName: "CPUUtilization", + Statistics: aws.StringSlice([]string{"Average", "p46.32"}), + Period: 600, + Id: "myId", }, } diff --git a/pkg/tsdb/cloudwatch/request_parser.go b/pkg/tsdb/cloudwatch/request_parser.go index 5da32d48231..2bd56a18e03 100644 --- a/pkg/tsdb/cloudwatch/request_parser.go +++ b/pkg/tsdb/cloudwatch/request_parser.go @@ -97,23 +97,21 @@ func parseRequestQuery(model *simplejson.Json, refId string) (*requestQuery, err returnData = true } - highResolution := model.Get("highResolution").MustBool(false) matchExact := model.Get("matchExact").MustBool(true) return &requestQuery{ - RefId: refId, - Region: region, - Namespace: namespace, - MetricName: metricName, - Dimensions: dimensions, - Statistics: aws.StringSlice(statistics), - Period: period, - Alias: alias, - Id: id, - Expression: expression, - ReturnData: returnData, - HighResolution: highResolution, - MatchExact: matchExact, + RefId: refId, + Region: region, + Namespace: namespace, + MetricName: metricName, + Dimensions: dimensions, + Statistics: aws.StringSlice(statistics), + Period: period, + Alias: alias, + Id: id, + Expression: expression, + ReturnData: returnData, + MatchExact: matchExact, }, nil } diff --git a/pkg/tsdb/cloudwatch/request_parser_test.go b/pkg/tsdb/cloudwatch/request_parser_test.go index 78692aa4329..82f1a32289e 100644 --- a/pkg/tsdb/cloudwatch/request_parser_test.go +++ b/pkg/tsdb/cloudwatch/request_parser_test.go @@ -22,10 +22,9 @@ func TestRequestParser(t *testing.T) { "InstanceId": []interface{}{"test"}, "InstanceType": []interface{}{"test2", "test3"}, }, - "statistics": []interface{}{"Average"}, - "period": "600", - "hide": false, - "highResolution": false, + "statistics": []interface{}{"Average"}, + "period": "600", + "hide": false, }) res, err := parseRequestQuery(query, "ref1") @@ -38,7 +37,6 @@ func TestRequestParser(t *testing.T) { So(res.Expression, ShouldEqual, "") So(res.Period, ShouldEqual, 600) So(res.ReturnData, ShouldEqual, true) - So(res.HighResolution, ShouldEqual, false) So(len(res.Dimensions), ShouldEqual, 2) So(len(res.Dimensions["InstanceId"]), ShouldEqual, 1) So(len(res.Dimensions["InstanceType"]), ShouldEqual, 2) @@ -59,10 +57,9 @@ func TestRequestParser(t *testing.T) { "InstanceId": "test", "InstanceType": "test2", }, - "statistics": []interface{}{"Average"}, - "period": "600", - "hide": false, - "highResolution": false, + "statistics": []interface{}{"Average"}, + "period": "600", + "hide": false, }) res, err := parseRequestQuery(query, "ref1") @@ -75,7 +72,6 @@ func TestRequestParser(t *testing.T) { So(res.Expression, ShouldEqual, "") So(res.Period, ShouldEqual, 600) So(res.ReturnData, ShouldEqual, true) - So(res.HighResolution, ShouldEqual, false) So(len(res.Dimensions), ShouldEqual, 2) So(len(res.Dimensions["InstanceId"]), ShouldEqual, 1) So(len(res.Dimensions["InstanceType"]), ShouldEqual, 1) diff --git a/pkg/tsdb/cloudwatch/response_parser.go b/pkg/tsdb/cloudwatch/response_parser.go index 23c17b6a3ba..3be09c3e7e3 100644 --- a/pkg/tsdb/cloudwatch/response_parser.go +++ b/pkg/tsdb/cloudwatch/response_parser.go @@ -63,7 +63,7 @@ func parseGetMetricDataTimeSeries(metricDataResults map[string]*cloudwatch.Metri result := tsdb.TimeSeriesSlice{} for label, metricDataResult := range metricDataResults { if *metricDataResult.StatusCode != "Complete" { - return nil, fmt.Errorf("too many datapoint requested in query %s. Please try to reduce the time range", query.RefId) + return nil, fmt.Errorf("too many datapoints requested in query %s. Please try to reduce the time range", query.RefId) } for _, message := range metricDataResult.Messages { diff --git a/pkg/tsdb/cloudwatch/types.go b/pkg/tsdb/cloudwatch/types.go index c225cdd35e9..eafda265e14 100644 --- a/pkg/tsdb/cloudwatch/types.go +++ b/pkg/tsdb/cloudwatch/types.go @@ -27,7 +27,6 @@ type requestQuery struct { ExtendedStatistics []*string Period int Alias string - HighResolution bool MatchExact bool } diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor.test.tsx index bf8c77bbff5..bb6205f345e 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor.test.tsx @@ -46,7 +46,6 @@ const setup = () => { period: '', expression: '', alias: '', - highResolution: false, matchExact: true, }, datasource, diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor.tsx index 9925a962e08..1cb547cd7a0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor.tsx @@ -51,12 +51,16 @@ export class QueryEditor extends PureComponent { query.region = 'default'; } - if (!query.statistics || !query.statistics.length) { - query.statistics = ['Average']; + if (!query.id) { + query.id = ''; } - if (!query.hasOwnProperty('highResolution')) { - query.highResolution = false; + if (!query.alias) { + query.alias = ''; + } + + if (!query.statistics || !query.statistics.length) { + query.statistics = ['Average']; } if (!query.hasOwnProperty('matchExact')) { @@ -198,11 +202,7 @@ export class QueryEditor extends PureComponent { )}
- + { > this.onChange({ ...query, alias: value })} /> - this.onChange({ ...query, highResolution: !query.highResolution })} - /> - Min Period + Period
-
- HighRes -
-
- - -
- -
-
-