2019-11-14 03:59:41 -06:00
|
|
|
package cloudwatch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-01-17 06:22:43 -06:00
|
|
|
"fmt"
|
2024-01-15 10:19:26 -06:00
|
|
|
"regexp"
|
2019-11-14 03:59:41 -06:00
|
|
|
|
2022-11-02 09:14:02 -05:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2024-02-07 06:53:05 -06:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
2024-01-30 06:11:52 -06:00
|
|
|
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/features"
|
2022-10-20 04:21:13 -05:00
|
|
|
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
2024-02-07 06:53:05 -06:00
|
|
|
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils"
|
2019-11-14 03:59:41 -06:00
|
|
|
)
|
|
|
|
|
2021-06-10 03:23:17 -05:00
|
|
|
type responseWrapper struct {
|
|
|
|
DataResponse *backend.DataResponse
|
|
|
|
RefId string
|
|
|
|
}
|
|
|
|
|
2024-02-07 06:53:05 -06:00
|
|
|
func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
|
|
|
e.logger.FromContext(ctx).Debug("Executing time series query")
|
2021-03-23 10:32:12 -05:00
|
|
|
resp := backend.NewQueryDataResponse()
|
2020-05-18 05:25:58 -05:00
|
|
|
|
2021-06-10 03:23:17 -05:00
|
|
|
if len(req.Queries) == 0 {
|
|
|
|
return nil, fmt.Errorf("request contains no queries")
|
|
|
|
}
|
|
|
|
// startTime and endTime are always the same for all queries
|
|
|
|
startTime := req.Queries[0].TimeRange.From
|
|
|
|
endTime := req.Queries[0].TimeRange.To
|
|
|
|
if !startTime.Before(endTime) {
|
|
|
|
return nil, fmt.Errorf("invalid time range: start time must be before end time")
|
|
|
|
}
|
2021-03-23 10:32:12 -05:00
|
|
|
|
2023-05-24 03:19:34 -05:00
|
|
|
instance, err := e.getInstance(ctx, req.PluginContext)
|
2022-12-13 14:24:28 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-07 06:53:05 -06:00
|
|
|
requestQueries, err := models.ParseMetricDataQueries(req.Queries, startTime, endTime, instance.Settings.Region, e.logger.FromContext(ctx),
|
2024-01-30 06:11:52 -06:00
|
|
|
features.IsEnabled(ctx, features.FlagCloudWatchCrossAccountQuerying))
|
2021-06-10 03:23:17 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-03-23 10:32:12 -05:00
|
|
|
|
2022-10-24 07:57:32 -05:00
|
|
|
if len(requestQueries) == 0 {
|
2021-06-10 03:23:17 -05:00
|
|
|
return backend.NewQueryDataResponse(), nil
|
|
|
|
}
|
2021-03-23 10:32:12 -05:00
|
|
|
|
2022-10-24 07:57:32 -05:00
|
|
|
requestQueriesByRegion := make(map[string][]*models.CloudWatchQuery)
|
|
|
|
for _, query := range requestQueries {
|
|
|
|
if _, exist := requestQueriesByRegion[query.Region]; !exist {
|
|
|
|
requestQueriesByRegion[query.Region] = []*models.CloudWatchQuery{}
|
|
|
|
}
|
|
|
|
requestQueriesByRegion[query.Region] = append(requestQueriesByRegion[query.Region], query)
|
|
|
|
}
|
|
|
|
|
2021-06-10 03:23:17 -05:00
|
|
|
resultChan := make(chan *responseWrapper, len(req.Queries))
|
|
|
|
eg, ectx := errgroup.WithContext(ctx)
|
2023-10-20 14:09:41 -05:00
|
|
|
for r, regionQueries := range requestQueriesByRegion {
|
2021-06-10 03:23:17 -05:00
|
|
|
region := r
|
2023-10-20 14:09:41 -05:00
|
|
|
|
|
|
|
batches := [][]*models.CloudWatchQuery{regionQueries}
|
2024-01-30 06:11:52 -06:00
|
|
|
if features.IsEnabled(ctx, features.FlagCloudWatchBatchQueries) {
|
2024-02-07 06:53:05 -06:00
|
|
|
batches = getMetricQueryBatches(regionQueries, e.logger.FromContext(ctx))
|
2023-10-20 14:09:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, batch := range batches {
|
|
|
|
requestQueries := batch
|
|
|
|
eg.Go(func() error {
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
2024-02-07 06:53:05 -06:00
|
|
|
e.logger.FromContext(ctx).Error("Execute Get Metric Data Query Panic", "error", err, "stack", utils.Stack(1))
|
2023-10-20 14:09:41 -05:00
|
|
|
if theErr, ok := err.(error); ok {
|
|
|
|
resultChan <- &responseWrapper{
|
|
|
|
DataResponse: &backend.DataResponse{
|
|
|
|
Error: theErr,
|
|
|
|
},
|
|
|
|
}
|
2021-03-23 10:32:12 -05:00
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
2023-10-20 14:09:41 -05:00
|
|
|
}()
|
|
|
|
|
|
|
|
client, err := e.getCWClient(ctx, req.PluginContext, region)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
|
2024-04-05 10:57:56 -05:00
|
|
|
metricDataInput, err := e.buildMetricDataInput(ctx, startTime, endTime, requestQueries)
|
2023-10-20 14:09:41 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
|
2023-10-20 14:09:41 -05:00
|
|
|
mdo, err := e.executeRequest(ectx, client, metricDataInput)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
|
2024-04-05 10:57:56 -05:00
|
|
|
if !features.IsEnabled(ctx, features.FlagCloudWatchNewLabelParsing) {
|
|
|
|
requestQueries, err = e.getDimensionValuesForWildcards(ctx, region, client, requestQueries, instance.tagValueCache, instance.Settings.GrafanaSettings.ListMetricsPageLimit)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-10-20 14:09:41 -05:00
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
|
2024-04-05 10:57:56 -05:00
|
|
|
res, err := e.parseResponse(ctx, startTime, endTime, mdo, requestQueries)
|
2023-09-27 09:41:48 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
|
2023-10-20 14:09:41 -05:00
|
|
|
for _, responseWrapper := range res {
|
|
|
|
resultChan <- responseWrapper
|
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
|
2023-10-20 14:09:41 -05:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
}
|
2021-03-23 10:32:12 -05:00
|
|
|
|
2021-06-10 03:23:17 -05:00
|
|
|
if err := eg.Wait(); err != nil {
|
2021-08-18 02:37:13 -05:00
|
|
|
dataResponse := backend.DataResponse{
|
|
|
|
Error: fmt.Errorf("metric request error: %q", err),
|
|
|
|
}
|
|
|
|
resultChan <- &responseWrapper{
|
2024-01-15 10:19:26 -06:00
|
|
|
RefId: getQueryRefIdFromErrorString(err.Error(), requestQueries),
|
2021-08-18 02:37:13 -05:00
|
|
|
DataResponse: &dataResponse,
|
|
|
|
}
|
2021-06-10 03:23:17 -05:00
|
|
|
}
|
|
|
|
close(resultChan)
|
2021-03-23 10:32:12 -05:00
|
|
|
|
2021-06-10 03:23:17 -05:00
|
|
|
for result := range resultChan {
|
|
|
|
resp.Responses[result.RefId] = *result.DataResponse
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
2021-03-23 10:32:12 -05:00
|
|
|
|
|
|
|
return resp, nil
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
2024-01-15 10:19:26 -06:00
|
|
|
|
|
|
|
func getQueryRefIdFromErrorString(err string, queries []*models.CloudWatchQuery) string {
|
|
|
|
// error can be in format "Error in expression 'test': Invalid syntax"
|
|
|
|
// so we can find the query id or ref id between the quotations
|
|
|
|
erroredRefId := ""
|
|
|
|
|
|
|
|
for _, query := range queries {
|
|
|
|
if regexp.MustCompile(`'`+query.RefId+`':`).MatchString(err) || regexp.MustCompile(`'`+query.Id+`':`).MatchString(err) {
|
|
|
|
erroredRefId = query.RefId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if errorRefId is empty, it means the error concerns all queries (error metric limit exceeded, for example)
|
|
|
|
return erroredRefId
|
|
|
|
}
|